parser 2.6.5.0 → 2.7.2.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/lib/parser.rb +4 -1
  3. data/lib/parser/all.rb +1 -0
  4. data/lib/parser/ast/processor.rb +21 -0
  5. data/lib/parser/base.rb +25 -5
  6. data/lib/parser/builders/default.rb +394 -24
  7. data/lib/parser/context.rb +5 -0
  8. data/lib/parser/current.rb +16 -7
  9. data/lib/parser/current_arg_stack.rb +43 -0
  10. data/lib/parser/diagnostic.rb +1 -1
  11. data/lib/parser/diagnostic/engine.rb +1 -2
  12. data/lib/parser/lexer.rb +23770 -0
  13. data/lib/parser/lexer/dedenter.rb +52 -49
  14. data/lib/parser/macruby.rb +6149 -0
  15. data/lib/parser/{lexer/max_numparam_stack.rb → max_numparam_stack.rb} +10 -4
  16. data/lib/parser/messages.rb +52 -29
  17. data/lib/parser/meta.rb +10 -5
  18. data/lib/parser/ruby18.rb +5663 -0
  19. data/lib/parser/ruby19.rb +6092 -0
  20. data/lib/parser/ruby20.rb +6527 -0
  21. data/lib/parser/ruby21.rb +6578 -0
  22. data/lib/parser/ruby22.rb +6613 -0
  23. data/lib/parser/ruby23.rb +6624 -0
  24. data/lib/parser/ruby24.rb +6694 -0
  25. data/lib/parser/ruby25.rb +6662 -0
  26. data/lib/parser/ruby26.rb +6676 -0
  27. data/lib/parser/ruby27.rb +7803 -0
  28. data/lib/parser/ruby28.rb +8047 -0
  29. data/lib/parser/ruby30.rb +8052 -0
  30. data/lib/parser/rubymotion.rb +6086 -0
  31. data/lib/parser/runner.rb +26 -2
  32. data/lib/parser/runner/ruby_rewrite.rb +2 -2
  33. data/lib/parser/source/buffer.rb +3 -1
  34. data/lib/parser/source/comment.rb +1 -1
  35. data/lib/parser/source/comment/associator.rb +14 -4
  36. data/lib/parser/source/map/method_definition.rb +25 -0
  37. data/lib/parser/source/range.rb +19 -3
  38. data/lib/parser/source/tree_rewriter.rb +115 -12
  39. data/lib/parser/source/tree_rewriter/action.rb +137 -28
  40. data/lib/parser/static_environment.rb +10 -0
  41. data/lib/parser/tree_rewriter.rb +1 -2
  42. data/lib/parser/variables_stack.rb +32 -0
  43. data/lib/parser/version.rb +1 -1
  44. data/parser.gemspec +10 -18
  45. metadata +22 -99
  46. data/.gitignore +0 -33
  47. data/.travis.yml +0 -45
  48. data/.yardopts +0 -21
  49. data/CHANGELOG.md +0 -997
  50. data/CONTRIBUTING.md +0 -17
  51. data/Gemfile +0 -10
  52. data/LICENSE.txt +0 -25
  53. data/README.md +0 -301
  54. data/Rakefile +0 -166
  55. data/ci/run_rubocop_specs +0 -14
  56. data/doc/AST_FORMAT.md +0 -1816
  57. data/doc/CUSTOMIZATION.md +0 -37
  58. data/doc/INTERNALS.md +0 -21
  59. data/doc/css/.gitkeep +0 -0
  60. data/doc/css/common.css +0 -68
  61. data/lib/parser/lexer.rl +0 -2533
  62. data/lib/parser/macruby.y +0 -2198
  63. data/lib/parser/ruby18.y +0 -1934
  64. data/lib/parser/ruby19.y +0 -2175
  65. data/lib/parser/ruby20.y +0 -2353
  66. data/lib/parser/ruby21.y +0 -2357
  67. data/lib/parser/ruby22.y +0 -2364
  68. data/lib/parser/ruby23.y +0 -2370
  69. data/lib/parser/ruby24.y +0 -2408
  70. data/lib/parser/ruby25.y +0 -2405
  71. data/lib/parser/ruby26.y +0 -2413
  72. data/lib/parser/ruby27.y +0 -2470
  73. data/lib/parser/rubymotion.y +0 -2182
  74. data/test/bug_163/fixtures/input.rb +0 -5
  75. data/test/bug_163/fixtures/output.rb +0 -5
  76. data/test/bug_163/rewriter.rb +0 -20
  77. data/test/helper.rb +0 -59
  78. data/test/parse_helper.rb +0 -316
  79. data/test/racc_coverage_helper.rb +0 -133
  80. data/test/test_base.rb +0 -31
  81. data/test/test_current.rb +0 -29
  82. data/test/test_diagnostic.rb +0 -96
  83. data/test/test_diagnostic_engine.rb +0 -62
  84. data/test/test_encoding.rb +0 -99
  85. data/test/test_lexer.rb +0 -3667
  86. data/test/test_lexer_stack_state.rb +0 -78
  87. data/test/test_parse_helper.rb +0 -80
  88. data/test/test_parser.rb +0 -7644
  89. data/test/test_runner_parse.rb +0 -35
  90. data/test/test_runner_rewrite.rb +0 -47
  91. data/test/test_source_buffer.rb +0 -162
  92. data/test/test_source_comment.rb +0 -36
  93. data/test/test_source_comment_associator.rb +0 -367
  94. data/test/test_source_map.rb +0 -15
  95. data/test/test_source_range.rb +0 -172
  96. data/test/test_source_rewriter.rb +0 -541
  97. data/test/test_source_rewriter_action.rb +0 -46
  98. data/test/test_source_tree_rewriter.rb +0 -173
  99. data/test/test_static_environment.rb +0 -45
  100. data/test/using_tree_rewriter/fixtures/input.rb +0 -3
  101. data/test/using_tree_rewriter/fixtures/output.rb +0 -3
  102. 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
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'tempfile'
4
- require 'minitest/test'
5
-
6
- require 'simplecov'
7
-
8
- if ENV.include?('COVERAGE') && SimpleCov.usable?
9
- require_relative 'racc_coverage_helper'
10
-
11
- RaccCoverage.start(
12
- %w(
13
- ruby18.y
14
- ruby19.y
15
- ruby20.y
16
- ruby21.y
17
- ruby22.y
18
- ruby23.y
19
- ruby24.y
20
- ruby25.y
21
- ruby26.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[
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
- class Parser::AST::Node
55
- def initialize(type, *)
56
- raise "Type #{type} missing from Parser::Meta::NODE_TYPES" unless Parser::Meta::NODE_TYPES.include?(type)
57
- super
58
- end
59
- end
@@ -1,316 +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 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 'mac' then parser = Parser::MacRuby.new
31
- when 'ios' then parser = Parser::RubyMotion.new
32
- else raise "Unrecognized Ruby version #{version}"
33
- end
34
-
35
- parser.diagnostics.consumer = lambda do |diagnostic|
36
- @diagnostics << diagnostic
37
- end
38
-
39
- parser
40
- end
41
-
42
- def with_versions(versions)
43
- (versions & ALL_VERSIONS).each do |version|
44
- @diagnostics.clear
45
-
46
- parser = parser_for_ruby_version(version)
47
- yield version, parser
48
- end
49
- end
50
-
51
- def assert_source_range(begin_pos, end_pos, range, version, what)
52
- assert range.is_a?(Parser::Source::Range),
53
- "(#{version}) #{range.inspect}.is_a?(Source::Range) for #{what}"
54
-
55
- assert_equal begin_pos, range.begin_pos,
56
- "(#{version}) begin of #{what}"
57
-
58
- assert_equal end_pos, range.end_pos,
59
- "(#{version}) end of #{what}"
60
- end
61
-
62
- # Use like this:
63
- # ~~~
64
- # assert_parses(
65
- # s(:send, s(:lit, 10), :+, s(:lit, 20))
66
- # %q{10 + 20},
67
- # %q{~~~~~~~ expression
68
- # | ^ operator
69
- # | ~~ expression (lit)
70
- # },
71
- # %w(1.8 1.9) # optional
72
- # )
73
- # ~~~
74
- def assert_parses(ast, code, source_maps='', versions=ALL_VERSIONS)
75
- with_versions(versions) do |version, parser|
76
- try_parsing(ast, code, parser, source_maps, version)
77
- end
78
-
79
- # Also try parsing with lexer set to use UTF-32LE internally
80
- with_versions(versions) do |version, parser|
81
- parser.instance_eval { @lexer.force_utf32 = true }
82
- try_parsing(ast, code, parser, source_maps, version)
83
- end
84
- end
85
-
86
- def try_parsing(ast, code, parser, source_maps, version)
87
- source_file = Parser::Source::Buffer.new('(assert_parses)')
88
- source_file.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) \
109
- do |begin_pos, end_pos, map_field, ast_path, line|
110
-
111
- astlet = traverse_ast(parsed_ast, ast_path)
112
-
113
- if astlet.nil?
114
- # This is a testsuite bug.
115
- raise "No entity with AST path #{ast_path} in #{parsed_ast.inspect}"
116
- end
117
-
118
- assert astlet.frozen?
119
-
120
- assert astlet.location.respond_to?(map_field),
121
- "(#{version}) #{astlet.location.inspect}.respond_to?(#{map_field.inspect}) for:\n#{parsed_ast.inspect}"
122
-
123
- range = astlet.location.send(map_field)
124
-
125
- assert_source_range(begin_pos, end_pos, range, version, line.inspect)
126
- end
127
-
128
- assert parser.instance_eval { @lexer }.cmdarg.empty?,
129
- "(#{version}) expected cmdarg to be empty after parsing"
130
- end
131
-
132
- # Use like this:
133
- # ~~~
134
- # assert_diagnoses(
135
- # [:warning, :ambiguous_prefix, { prefix: '*' }],
136
- # %q{foo *bar},
137
- # %q{ ^ location
138
- # | ~~~ highlights (0)})
139
- # ~~~
140
- def assert_diagnoses(diagnostic, code, source_maps='', versions=ALL_VERSIONS)
141
- with_versions(versions) do |version, parser|
142
- source_file = Parser::Source::Buffer.new('(assert_diagnoses)')
143
- source_file.source = code
144
-
145
- begin
146
- parser = parser.parse(source_file)
147
- rescue Parser::SyntaxError
148
- # do nothing; the diagnostic was reported
149
- end
150
-
151
- assert_equal 1, @diagnostics.count,
152
- "(#{version}) emits a single diagnostic, not\n" \
153
- "#{@diagnostics.map(&:render).join("\n")}"
154
-
155
- emitted_diagnostic = @diagnostics.first
156
-
157
- level, reason, arguments = diagnostic
158
- arguments ||= {}
159
- message = Parser::MESSAGES[reason] % arguments
160
-
161
- assert_equal level, emitted_diagnostic.level
162
- assert_equal reason, emitted_diagnostic.reason
163
- assert_equal arguments, emitted_diagnostic.arguments
164
- assert_equal message, emitted_diagnostic.message
165
-
166
- parse_source_map_descriptions(source_maps) \
167
- 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)')
202
- source_file.source = code
203
-
204
- begin
205
- parser = parser.parse(source_file)
206
- rescue Parser::SyntaxError
207
- # do nothing; the diagnostic was reported
208
- end
209
-
210
- assert_equal diagnostics.count, @diagnostics.count
211
-
212
- diagnostics.zip(@diagnostics) do |expected_diagnostic, actual_diagnostic|
213
- level, reason, arguments = expected_diagnostic
214
- arguments ||= {}
215
- message = Parser::MESSAGES[reason] % arguments
216
-
217
- assert_equal level, actual_diagnostic.level
218
- assert_equal reason, actual_diagnostic.reason
219
- assert_equal arguments, actual_diagnostic.arguments
220
- assert_equal message, actual_diagnostic.message
221
- end
222
- end
223
- end
224
-
225
- def refute_diagnoses(code, versions=ALL_VERSIONS)
226
- with_versions(versions) do |version, parser|
227
- source_file = Parser::Source::Buffer.new('(refute_diagnoses)')
228
- source_file.source = code
229
-
230
- begin
231
- parser = parser.parse(source_file)
232
- rescue Parser::SyntaxError
233
- # do nothing; the diagnostic was reported
234
- end
235
-
236
- assert_empty @diagnostics,
237
- "(#{version}) emits no diagnostics, not\n" \
238
- "#{@diagnostics.map(&:render).join("\n")}"
239
- end
240
- end
241
-
242
- def assert_context(context, code, versions=ALL_VERSIONS)
243
- with_versions(versions) do |version, parser|
244
- source_file = Parser::Source::Buffer.new('(assert_context)')
245
- source_file.source = code
246
-
247
- begin
248
- parser.parse(source_file)
249
- rescue Parser::SyntaxError
250
- # do nothing; the diagnostic was reported
251
- end
252
-
253
- assert_equal parser.context.stack, context, "(#{version}) parsing context"
254
- end
255
- end
256
-
257
- SOURCE_MAP_DESCRIPTION_RE =
258
- /(?x)
259
- ^(?# $1 skip) ^(\s*)
260
- (?# $2 highlight) ([~\^]+)
261
- \s+
262
- (?# $3 source_map_field) ([a-z_]+)
263
- (?# $5 ast_path) (\s+\(([a-z_.\/0-9]+)\))?
264
- $/
265
-
266
- def parse_source_map_descriptions(descriptions)
267
- unless block_given?
268
- return to_enum(:parse_source_map_descriptions, descriptions)
269
- end
270
-
271
- descriptions.each_line do |line|
272
- # Remove leading " |", if it exists.
273
- line = line.sub(/^\s*\|/, '').rstrip
274
-
275
- next if line.empty?
276
-
277
- if (match = SOURCE_MAP_DESCRIPTION_RE.match(line))
278
- begin_pos = match[1].length
279
- end_pos = begin_pos + match[2].length
280
- source_map_field = match[3]
281
-
282
- if match[5]
283
- ast_path = match[5].split('.')
284
- else
285
- ast_path = []
286
- end
287
-
288
- yield begin_pos, end_pos, source_map_field, ast_path, line
289
- else
290
- raise "Cannot parse source map description line: #{line.inspect}."
291
- end
292
- end
293
- end
294
-
295
- def traverse_ast(ast, path)
296
- path.inject(ast) do |astlet, path_component|
297
- # Split "dstr/2" to :dstr and 1
298
- type_str, index_str = path_component.split('/')
299
-
300
- type = type_str.to_sym
301
-
302
- if index_str.nil?
303
- index = 0
304
- else
305
- index = index_str.to_i - 1
306
- end
307
-
308
- matching_children = \
309
- astlet.children.select do |child|
310
- AST::Node === child && child.type == type
311
- end
312
-
313
- matching_children[index]
314
- end
315
- end
316
- 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