parser 2.7.1.3 → 3.0.1.0

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/lib/parser.rb +1 -1
  3. data/lib/parser/all.rb +1 -1
  4. data/lib/parser/ast/processor.rb +5 -7
  5. data/lib/parser/base.rb +7 -5
  6. data/lib/parser/builders/default.rb +225 -29
  7. data/lib/parser/context.rb +5 -0
  8. data/lib/parser/current.rb +11 -11
  9. data/lib/parser/current_arg_stack.rb +5 -2
  10. data/lib/parser/lexer.rb +23780 -0
  11. data/lib/parser/macruby.rb +6149 -0
  12. data/lib/parser/max_numparam_stack.rb +13 -5
  13. data/lib/parser/messages.rb +3 -0
  14. data/lib/parser/meta.rb +8 -7
  15. data/lib/parser/ruby18.rb +5667 -0
  16. data/lib/parser/ruby19.rb +6092 -0
  17. data/lib/parser/ruby20.rb +6527 -0
  18. data/lib/parser/ruby21.rb +6578 -0
  19. data/lib/parser/ruby22.rb +6613 -0
  20. data/lib/parser/ruby23.rb +6624 -0
  21. data/lib/parser/ruby24.rb +6694 -0
  22. data/lib/parser/ruby25.rb +6662 -0
  23. data/lib/parser/ruby26.rb +6676 -0
  24. data/lib/parser/ruby27.rb +7862 -0
  25. data/lib/parser/ruby28.rb +8047 -0
  26. data/lib/parser/ruby30.rb +8060 -0
  27. data/lib/parser/rubymotion.rb +6086 -0
  28. data/lib/parser/runner.rb +4 -4
  29. data/lib/parser/source/buffer.rb +50 -27
  30. data/lib/parser/source/comment.rb +1 -1
  31. data/lib/parser/source/comment/associator.rb +1 -1
  32. data/lib/parser/source/map/{endless_definition.rb → method_definition.rb} +5 -3
  33. data/lib/parser/source/range.rb +3 -3
  34. data/lib/parser/source/tree_rewriter.rb +94 -1
  35. data/lib/parser/source/tree_rewriter/action.rb +39 -0
  36. data/lib/parser/static_environment.rb +4 -0
  37. data/lib/parser/variables_stack.rb +4 -0
  38. data/lib/parser/version.rb +1 -1
  39. data/parser.gemspec +2 -18
  40. metadata +13 -102
  41. data/.gitignore +0 -34
  42. data/.travis.yml +0 -40
  43. data/.yardopts +0 -21
  44. data/CHANGELOG.md +0 -1101
  45. data/CONTRIBUTING.md +0 -17
  46. data/Gemfile +0 -10
  47. data/README.md +0 -308
  48. data/Rakefile +0 -167
  49. data/ci/run_rubocop_specs +0 -14
  50. data/doc/AST_FORMAT.md +0 -2229
  51. data/doc/CUSTOMIZATION.md +0 -37
  52. data/doc/INTERNALS.md +0 -21
  53. data/doc/css/.gitkeep +0 -0
  54. data/doc/css/common.css +0 -68
  55. data/lib/parser/lexer.rl +0 -2543
  56. data/lib/parser/macruby.y +0 -2198
  57. data/lib/parser/ruby18.y +0 -1934
  58. data/lib/parser/ruby19.y +0 -2175
  59. data/lib/parser/ruby20.y +0 -2353
  60. data/lib/parser/ruby21.y +0 -2357
  61. data/lib/parser/ruby22.y +0 -2364
  62. data/lib/parser/ruby23.y +0 -2370
  63. data/lib/parser/ruby24.y +0 -2408
  64. data/lib/parser/ruby25.y +0 -2405
  65. data/lib/parser/ruby26.y +0 -2413
  66. data/lib/parser/ruby27.y +0 -2941
  67. data/lib/parser/ruby28.y +0 -3016
  68. data/lib/parser/rubymotion.y +0 -2182
  69. data/test/bug_163/fixtures/input.rb +0 -5
  70. data/test/bug_163/fixtures/output.rb +0 -5
  71. data/test/bug_163/rewriter.rb +0 -20
  72. data/test/helper.rb +0 -79
  73. data/test/parse_helper.rb +0 -313
  74. data/test/racc_coverage_helper.rb +0 -133
  75. data/test/test_ast_processor.rb +0 -32
  76. data/test/test_base.rb +0 -31
  77. data/test/test_current.rb +0 -31
  78. data/test/test_diagnostic.rb +0 -95
  79. data/test/test_diagnostic_engine.rb +0 -59
  80. data/test/test_encoding.rb +0 -99
  81. data/test/test_lexer.rb +0 -3617
  82. data/test/test_lexer_stack_state.rb +0 -78
  83. data/test/test_meta.rb +0 -12
  84. data/test/test_parse_helper.rb +0 -80
  85. data/test/test_parser.rb +0 -9596
  86. data/test/test_runner_parse.rb +0 -56
  87. data/test/test_runner_rewrite.rb +0 -47
  88. data/test/test_source_buffer.rb +0 -165
  89. data/test/test_source_comment.rb +0 -36
  90. data/test/test_source_comment_associator.rb +0 -399
  91. data/test/test_source_map.rb +0 -14
  92. data/test/test_source_range.rb +0 -192
  93. data/test/test_source_rewriter.rb +0 -541
  94. data/test/test_source_rewriter_action.rb +0 -46
  95. data/test/test_source_tree_rewriter.rb +0 -263
  96. data/test/test_static_environment.rb +0 -45
  97. data/test/using_tree_rewriter/fixtures/input.rb +0 -3
  98. data/test/using_tree_rewriter/fixtures/output.rb +0 -3
  99. data/test/using_tree_rewriter/using_tree_rewriter.rb +0 -9
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if(true)
4
- puts "Hello, world!"
5
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if true
4
- puts "Hello, world!"
5
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Rewriter < Parser::Rewriter
4
- def on_if(node)
5
- # Crude, totally-not-usable-in-the-real-world code to remove optional
6
- # parens from control keywords.
7
- #
8
- # In a perfect test scenario we'd simply make this a no-op, to demonstrate
9
- # that the bug happens when any rewriter is loaded regardless of whether it
10
- # actually changes anything but that makes assertions much harder to get
11
- # right. It's much easier to just show that the file did, or did not
12
- # get changed.
13
- if node.children[0].type == :begin
14
- replace node.children[0].loc.begin, ' '
15
- remove node.children[0].loc.end
16
- end
17
-
18
- super
19
- end
20
- end
data/test/helper.rb DELETED
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'tempfile'
4
- require 'simplecov'
5
-
6
- if ENV.include?('COVERAGE') && SimpleCov.usable?
7
- require_relative 'racc_coverage_helper'
8
-
9
- RaccCoverage.start(
10
- %w(
11
- ruby18.y
12
- ruby19.y
13
- ruby20.y
14
- ruby21.y
15
- ruby22.y
16
- ruby23.y
17
- ruby24.y
18
- ruby25.y
19
- ruby26.y
20
- ruby27.y
21
- ruby28.y
22
- ),
23
- File.expand_path('../../lib/parser', __FILE__))
24
-
25
- # Report results faster.
26
- at_exit { RaccCoverage.stop }
27
-
28
- SimpleCov.start do
29
- self.formatter = SimpleCov::Formatter::MultiFormatter.new(
30
- SimpleCov::Formatter::HTMLFormatter
31
- )
32
-
33
- add_group 'Grammars' do |source_file|
34
- source_file.filename =~ %r{\.y$}
35
- end
36
-
37
- # Exclude the testsuite itself.
38
- add_filter '/test/'
39
-
40
- # Exclude generated files.
41
- add_filter do |source_file|
42
- source_file.filename =~ %r{/lib/parser/(lexer|ruby\d+|macruby|rubymotion)\.rb$}
43
- end
44
- end
45
- end
46
-
47
- # minitest/autorun must go after SimpleCov to preserve
48
- # correct order of at_exit hooks.
49
- require 'minitest/autorun'
50
-
51
- $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
52
- require 'parser'
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
-
74
- class Parser::AST::Node
75
- def initialize(type, *)
76
- NodeCollector.nodes << self
77
- super
78
- end
79
- end
data/test/parse_helper.rb DELETED
@@ -1,313 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ParseHelper
4
- include AST::Sexp
5
-
6
- require 'parser/all'
7
- require 'parser/macruby'
8
- require 'parser/rubymotion'
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 2.8 mac ios)
11
-
12
- def setup
13
- @diagnostics = []
14
-
15
- super if defined?(super)
16
- end
17
-
18
- def parser_for_ruby_version(version)
19
- case version
20
- when '1.8' then parser = Parser::Ruby18.new
21
- when '1.9' then parser = Parser::Ruby19.new
22
- when '2.0' then parser = Parser::Ruby20.new
23
- when '2.1' then parser = Parser::Ruby21.new
24
- when '2.2' then parser = Parser::Ruby22.new
25
- when '2.3' then parser = Parser::Ruby23.new
26
- when '2.4' then parser = Parser::Ruby24.new
27
- when '2.5' then parser = Parser::Ruby25.new
28
- when '2.6' then parser = Parser::Ruby26.new
29
- when '2.7' then parser = Parser::Ruby27.new
30
- when '2.8' then parser = Parser::Ruby28.new
31
- when 'mac' then parser = Parser::MacRuby.new
32
- when 'ios' then parser = Parser::RubyMotion.new
33
- else raise "Unrecognized Ruby version #{version}"
34
- end
35
-
36
- parser.diagnostics.consumer = lambda do |diagnostic|
37
- @diagnostics << diagnostic
38
- end
39
-
40
- parser
41
- end
42
-
43
- def with_versions(versions)
44
- (versions & ALL_VERSIONS).each do |version|
45
- @diagnostics.clear
46
-
47
- parser = parser_for_ruby_version(version)
48
- yield version, parser
49
- end
50
- end
51
-
52
- def assert_source_range(begin_pos, end_pos, range, version, what)
53
- assert range.is_a?(Parser::Source::Range),
54
- "(#{version}) #{range.inspect}.is_a?(Source::Range) for #{what}"
55
-
56
- assert_equal begin_pos, range.begin_pos,
57
- "(#{version}) begin of #{what}"
58
-
59
- assert_equal end_pos, range.end_pos,
60
- "(#{version}) end of #{what}"
61
- end
62
-
63
- # Use like this:
64
- # ~~~
65
- # assert_parses(
66
- # s(:send, s(:lit, 10), :+, s(:lit, 20))
67
- # %q{10 + 20},
68
- # %q{~~~~~~~ expression
69
- # | ^ operator
70
- # | ~~ expression (lit)
71
- # },
72
- # %w(1.8 1.9) # optional
73
- # )
74
- # ~~~
75
- def assert_parses(ast, code, source_maps='', versions=ALL_VERSIONS)
76
- with_versions(versions) do |version, parser|
77
- try_parsing(ast, code, parser, source_maps, version)
78
- end
79
-
80
- # Also try parsing with lexer set to use UTF-32LE internally
81
- with_versions(versions) do |version, parser|
82
- parser.instance_eval { @lexer.force_utf32 = true }
83
- try_parsing(ast, code, parser, source_maps, version)
84
- end
85
- end
86
-
87
- def try_parsing(ast, code, parser, source_maps, version)
88
- source_file = Parser::Source::Buffer.new('(assert_parses)', source: code)
89
-
90
- begin
91
- parsed_ast = parser.parse(source_file)
92
- rescue => exc
93
- backtrace = exc.backtrace
94
- Exception.instance_method(:initialize).bind(exc).
95
- call("(#{version}) #{exc.message}")
96
- exc.set_backtrace(backtrace)
97
- raise
98
- end
99
-
100
- if ast.nil?
101
- assert_nil parsed_ast, "(#{version}) AST equality"
102
- return
103
- end
104
-
105
- assert_equal ast, parsed_ast,
106
- "(#{version}) AST equality"
107
-
108
- parse_source_map_descriptions(source_maps) do |begin_pos, end_pos, map_field, ast_path, line|
109
-
110
- astlet = traverse_ast(parsed_ast, ast_path)
111
-
112
- if astlet.nil?
113
- # This is a testsuite bug.
114
- raise "No entity with AST path #{ast_path} in #{parsed_ast.inspect}"
115
- end
116
-
117
- assert astlet.frozen?
118
-
119
- assert astlet.location.respond_to?(map_field),
120
- "(#{version}) #{astlet.location.inspect}.respond_to?(#{map_field.inspect}) for:\n#{parsed_ast.inspect}"
121
-
122
- range = astlet.location.send(map_field)
123
-
124
- assert_source_range(begin_pos, end_pos, range, version, line.inspect)
125
- end
126
-
127
- assert parser.instance_eval { @lexer }.cmdarg.empty?,
128
- "(#{version}) expected cmdarg to be empty after parsing"
129
-
130
- assert_equal 0, parser.instance_eval { @lexer.instance_eval { @paren_nest } },
131
- "(#{version}) expected paren_nest to be 0 after parsing"
132
- end
133
-
134
- # Use like this:
135
- # ~~~
136
- # assert_diagnoses(
137
- # [:warning, :ambiguous_prefix, { prefix: '*' }],
138
- # %q{foo *bar},
139
- # %q{ ^ location
140
- # | ~~~ highlights (0)})
141
- # ~~~
142
- def assert_diagnoses(diagnostic, code, source_maps='', versions=ALL_VERSIONS)
143
- with_versions(versions) do |version, parser|
144
- source_file = Parser::Source::Buffer.new('(assert_diagnoses)', source: code)
145
-
146
- begin
147
- parser = parser.parse(source_file)
148
- rescue Parser::SyntaxError
149
- # do nothing; the diagnostic was reported
150
- end
151
-
152
- assert_equal 1, @diagnostics.count,
153
- "(#{version}) emits a single diagnostic, not\n" \
154
- "#{@diagnostics.map(&:render).join("\n")}"
155
-
156
- emitted_diagnostic = @diagnostics.first
157
-
158
- level, reason, arguments = diagnostic
159
- arguments ||= {}
160
- message = Parser::Messages.compile(reason, arguments)
161
-
162
- assert_equal level, emitted_diagnostic.level
163
- assert_equal reason, emitted_diagnostic.reason
164
- assert_equal arguments, emitted_diagnostic.arguments
165
- assert_equal message, emitted_diagnostic.message
166
-
167
- parse_source_map_descriptions(source_maps) do |begin_pos, end_pos, map_field, ast_path, line|
168
-
169
- case map_field
170
- when 'location'
171
- assert_source_range begin_pos, end_pos,
172
- emitted_diagnostic.location,
173
- version, 'location'
174
-
175
- when 'highlights'
176
- index = ast_path.first.to_i
177
-
178
- assert_source_range begin_pos, end_pos,
179
- emitted_diagnostic.highlights[index],
180
- version, "#{index}th highlight"
181
-
182
- else
183
- raise "Unknown diagnostic range #{map_field}"
184
- end
185
- end
186
- end
187
- end
188
-
189
- # Use like this:
190
- # ~~~
191
- # assert_diagnoses_many(
192
- # [
193
- # [:warning, :ambiguous_literal],
194
- # [:error, :unexpected_token, { :token => :tLCURLY }]
195
- # ],
196
- # %q{m /foo/ {}},
197
- # SINCE_2_4)
198
- # ~~~
199
- def assert_diagnoses_many(diagnostics, code, versions=ALL_VERSIONS)
200
- with_versions(versions) do |version, parser|
201
- source_file = Parser::Source::Buffer.new('(assert_diagnoses_many)', source: code)
202
-
203
- begin
204
- parser = parser.parse(source_file)
205
- rescue Parser::SyntaxError
206
- # do nothing; the diagnostic was reported
207
- end
208
-
209
- assert_equal diagnostics.count, @diagnostics.count
210
-
211
- diagnostics.zip(@diagnostics) do |expected_diagnostic, actual_diagnostic|
212
- level, reason, arguments = expected_diagnostic
213
- arguments ||= {}
214
- message = Parser::Messages.compile(reason, arguments)
215
-
216
- assert_equal level, actual_diagnostic.level
217
- assert_equal reason, actual_diagnostic.reason
218
- assert_equal arguments, actual_diagnostic.arguments
219
- assert_equal message, actual_diagnostic.message
220
- end
221
- end
222
- end
223
-
224
- def refute_diagnoses(code, versions=ALL_VERSIONS)
225
- with_versions(versions) do |version, parser|
226
- source_file = Parser::Source::Buffer.new('(refute_diagnoses)', source: code)
227
-
228
- begin
229
- parser = parser.parse(source_file)
230
- rescue Parser::SyntaxError
231
- # do nothing; the diagnostic was reported
232
- end
233
-
234
- assert_empty @diagnostics,
235
- "(#{version}) emits no diagnostics, not\n" \
236
- "#{@diagnostics.map(&:render).join("\n")}"
237
- end
238
- end
239
-
240
- def assert_context(context, code, versions=ALL_VERSIONS)
241
- with_versions(versions) do |version, parser|
242
- source_file = Parser::Source::Buffer.new('(assert_context)', source: code)
243
-
244
- begin
245
- parser.parse(source_file)
246
- rescue Parser::SyntaxError
247
- # do nothing; the diagnostic was reported
248
- end
249
-
250
- assert_equal parser.context.stack, context, "(#{version}) parsing context"
251
- end
252
- end
253
-
254
- SOURCE_MAP_DESCRIPTION_RE =
255
- /(?x)
256
- ^(?# $1 skip) ^(\s*)
257
- (?# $2 highlight) ([~\^]+)
258
- \s+
259
- (?# $3 source_map_field) ([a-z_]+)
260
- (?# $5 ast_path) (\s+\(([a-z_.\/0-9]+)\))?
261
- $/
262
-
263
- def parse_source_map_descriptions(descriptions)
264
- unless block_given?
265
- return to_enum(:parse_source_map_descriptions, descriptions)
266
- end
267
-
268
- descriptions.each_line do |line|
269
- # Remove leading " |", if it exists.
270
- line = line.sub(/^\s*\|/, '').rstrip
271
-
272
- next if line.empty?
273
-
274
- if (match = SOURCE_MAP_DESCRIPTION_RE.match(line))
275
- begin_pos = match[1].length
276
- end_pos = begin_pos + match[2].length
277
- source_map_field = match[3]
278
-
279
- if match[5]
280
- ast_path = match[5].split('.')
281
- else
282
- ast_path = []
283
- end
284
-
285
- yield begin_pos, end_pos, source_map_field, ast_path, line
286
- else
287
- raise "Cannot parse source map description line: #{line.inspect}."
288
- end
289
- end
290
- end
291
-
292
- def traverse_ast(ast, path)
293
- path.inject(ast) do |astlet, path_component|
294
- # Split "dstr/2" to :dstr and 1
295
- type_str, index_str = path_component.split('/')
296
-
297
- type = type_str.to_sym
298
-
299
- if index_str.nil?
300
- index = 0
301
- else
302
- index = index_str.to_i - 1
303
- end
304
-
305
- matching_children = \
306
- astlet.children.select do |child|
307
- AST::Node === child && child.type == type
308
- end
309
-
310
- matching_children[index]
311
- end
312
- end
313
- end
@@ -1,133 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'racc/grammarfileparser'
4
-
5
- # Unfortunately, Ruby's Coverage module ignores module_eval statements,
6
- # which Racc uses to map `parser.y` locations in the generated
7
- # `parser.rb`.
8
- module RaccCoverage
9
- @coverage = {}
10
- @base_path = nil
11
- @trace = nil
12
-
13
- def self.start(parsers, base_path)
14
- @base_path = base_path
15
-
16
- parsers.each do |parser|
17
- @coverage[parser] = extract_interesting_lines(parser, base_path)
18
- end
19
-
20
- @trace = TracePoint.new(:line) do |trace|
21
- lineno = trace.lineno - 1
22
-
23
- if (line_coverage = @coverage[trace.path])
24
- if line_coverage[lineno]
25
- line_coverage[lineno] += 1
26
- end
27
- end
28
- end
29
- @trace.enable
30
- end
31
-
32
- def self.stop
33
- @trace.disable
34
- end
35
-
36
- # Ruby's TracePoint#lineno will point only on "interesting" lines,
37
- # i.e.: only code (no comments or empty lines), no `end` keywords,
38
- # and for multi-line statements, only the first line of the statement.
39
- #
40
- # This method implements a very dumb Ruby parser, which skips empty lines
41
- # or lines with just comments, `end` keywords, and correctly handles
42
- # multi-line statements of the following form:
43
- #
44
- # * All lines of the statement except the last must end with `,`, `.` or `(`.
45
- #
46
- # Coverage can be disabled for code regions with annotations :nocov: and :cov:.
47
- #
48
- # Also, for best results, all actions should be delimited by at least
49
- # one non-action line.
50
- #
51
- def self.extract_interesting_lines(parser, base_path)
52
- grammar_source = File.join(@base_path, parser)
53
- grammar_file = Racc::GrammarFileParser.parse_file(grammar_source)
54
-
55
- ruby_sources = [
56
- # Header and footer aren't passed through module_eval
57
- # in Racc-generated file, so the location info is lost.
58
- *grammar_file.params.inner,
59
- ].compact
60
-
61
- grammar_file.grammar.each_rule do |rule|
62
- source = rule.action.source
63
- next if source.nil?
64
-
65
- ruby_sources << source
66
- end
67
-
68
- lines = []
69
-
70
- ruby_sources.each do |source|
71
- first_line = source.lineno
72
-
73
- state = :first_line
74
-
75
- source.text.each_line.with_index do |line, index|
76
- line = line.strip
77
-
78
- continues = line.end_with?(',') ||
79
- line.end_with?('(') ||
80
- line.end_with?('.')
81
-
82
- case state
83
- when :first_line
84
- if line =~ /:nocov/
85
- state = :nocov
86
- next
87
- elsif line.empty? ||
88
- line == 'end' ||
89
- line.start_with?('#')
90
- next
91
- elsif continues
92
- state = :mid_line
93
- end
94
-
95
- lines[first_line + index - 1] = 0
96
-
97
- when :mid_line
98
- unless continues
99
- state = :first_line
100
- end
101
-
102
- when :nocov
103
- if line =~ /:cov:/
104
- state = :first_line
105
- end
106
- end
107
- end
108
- end
109
-
110
- lines
111
- end
112
-
113
- def self.result
114
- result =
115
- @coverage.map do |parser, coverage|
116
- [File.join(@base_path, parser), coverage]
117
- end
118
-
119
- Hash[result]
120
- end
121
- end
122
-
123
- class << SimpleCov
124
- def result_with_racc_coverage
125
- @result ||= SimpleCov::Result.new(
126
- Coverage.result.merge(RaccCoverage.result))
127
-
128
- result_without_racc_coverage
129
- end
130
-
131
- alias result_without_racc_coverage result
132
- alias result result_with_racc_coverage
133
- end