parser 2.6.5.0 → 2.7.2.0

Sign up to get free protection for your applications and to get access to all the features.
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