parser 2.7.1.5 → 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.
- checksums.yaml +4 -4
- data/lib/parser/current.rb +1 -1
- data/lib/parser/meta.rb +2 -2
- data/lib/parser/ruby28.rb +8047 -0
- data/lib/parser/version.rb +1 -1
- data/parser.gemspec +1 -20
- metadata +7 -96
- data/.travis.yml +0 -41
- data/.yardopts +0 -21
- data/CHANGELOG.md +0 -1137
- data/CONTRIBUTING.md +0 -17
- data/Gemfile +0 -10
- data/LICENSE.txt +0 -25
- data/README.md +0 -309
- data/Rakefile +0 -167
- data/ci/run_rubocop_specs +0 -14
- data/doc/AST_FORMAT.md +0 -2284
- data/doc/CUSTOMIZATION.md +0 -37
- data/doc/INTERNALS.md +0 -21
- data/doc/css/.gitkeep +0 -0
- data/doc/css/common.css +0 -68
- data/lib/parser/lexer.rl +0 -2550
- data/lib/parser/macruby.y +0 -2208
- data/lib/parser/ruby18.y +0 -1936
- data/lib/parser/ruby19.y +0 -2185
- data/lib/parser/ruby20.y +0 -2363
- data/lib/parser/ruby21.y +0 -2364
- data/lib/parser/ruby22.y +0 -2371
- data/lib/parser/ruby23.y +0 -2377
- data/lib/parser/ruby24.y +0 -2415
- data/lib/parser/ruby25.y +0 -2412
- data/lib/parser/ruby26.y +0 -2420
- data/lib/parser/ruby27.y +0 -2949
- data/lib/parser/ruby30.y +0 -3048
- data/lib/parser/rubymotion.y +0 -2192
- data/test/bug_163/fixtures/input.rb +0 -5
- data/test/bug_163/fixtures/output.rb +0 -5
- data/test/bug_163/rewriter.rb +0 -20
- data/test/helper.rb +0 -103
- data/test/parse_helper.rb +0 -328
- data/test/racc_coverage_helper.rb +0 -133
- data/test/test_ast_processor.rb +0 -32
- data/test/test_base.rb +0 -31
- data/test/test_current.rb +0 -31
- data/test/test_diagnostic.rb +0 -95
- data/test/test_diagnostic_engine.rb +0 -59
- data/test/test_encoding.rb +0 -99
- data/test/test_lexer.rb +0 -3617
- data/test/test_lexer_stack_state.rb +0 -78
- data/test/test_meta.rb +0 -12
- data/test/test_parse_helper.rb +0 -83
- data/test/test_parser.rb +0 -9986
- data/test/test_runner_parse.rb +0 -56
- data/test/test_runner_rewrite.rb +0 -47
- data/test/test_source_buffer.rb +0 -165
- data/test/test_source_comment.rb +0 -36
- data/test/test_source_comment_associator.rb +0 -399
- data/test/test_source_map.rb +0 -14
- data/test/test_source_range.rb +0 -192
- data/test/test_source_rewriter.rb +0 -541
- data/test/test_source_rewriter_action.rb +0 -46
- data/test/test_source_tree_rewriter.rb +0 -361
- data/test/test_static_environment.rb +0 -45
- data/test/using_tree_rewriter/fixtures/input.rb +0 -3
- data/test/using_tree_rewriter/fixtures/output.rb +0 -3
- data/test/using_tree_rewriter/using_tree_rewriter.rb +0 -9
data/test/bug_163/rewriter.rb
DELETED
@@ -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,103 +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
|
-
ruby30.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
|
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
|
data/test/parse_helper.rb
DELETED
@@ -1,328 +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 3.0 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 '3.0' then parser = Parser::Ruby30.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(expect_range, range, version, what)
|
53
|
-
if expect_range == nil
|
54
|
-
# Avoid "Use assert_nil if expecting nil from .... This will fail in Minitest 6.""
|
55
|
-
assert_nil range,
|
56
|
-
"(#{version}) range of #{what}"
|
57
|
-
else
|
58
|
-
assert range.is_a?(Parser::Source::Range),
|
59
|
-
"(#{version}) #{range.inspect}.is_a?(Source::Range) for #{what}"
|
60
|
-
assert_equal expect_range, range.to_range,
|
61
|
-
"(#{version}) range of #{what}"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Use like this:
|
66
|
-
# ~~~
|
67
|
-
# assert_parses(
|
68
|
-
# s(:send, s(:lit, 10), :+, s(:lit, 20))
|
69
|
-
# %q{10 + 20},
|
70
|
-
# %q{~~~~~~~ expression
|
71
|
-
# | ^ operator
|
72
|
-
# | ~~ expression (lit)
|
73
|
-
# },
|
74
|
-
# %w(1.8 1.9) # optional
|
75
|
-
# )
|
76
|
-
# ~~~
|
77
|
-
def assert_parses(ast, code, source_maps='', versions=ALL_VERSIONS)
|
78
|
-
with_versions(versions) do |version, parser|
|
79
|
-
try_parsing(ast, code, parser, source_maps, version)
|
80
|
-
end
|
81
|
-
|
82
|
-
# Also try parsing with lexer set to use UTF-32LE internally
|
83
|
-
with_versions(versions) do |version, parser|
|
84
|
-
parser.instance_eval { @lexer.force_utf32 = true }
|
85
|
-
try_parsing(ast, code, parser, source_maps, version)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def try_parsing(ast, code, parser, source_maps, version)
|
90
|
-
source_file = Parser::Source::Buffer.new('(assert_parses)', source: code)
|
91
|
-
|
92
|
-
begin
|
93
|
-
parsed_ast = parser.parse(source_file)
|
94
|
-
rescue => exc
|
95
|
-
backtrace = exc.backtrace
|
96
|
-
Exception.instance_method(:initialize).bind(exc).
|
97
|
-
call("(#{version}) #{exc.message}")
|
98
|
-
exc.set_backtrace(backtrace)
|
99
|
-
raise
|
100
|
-
end
|
101
|
-
|
102
|
-
if ast.nil?
|
103
|
-
assert_nil parsed_ast, "(#{version}) AST equality"
|
104
|
-
return
|
105
|
-
end
|
106
|
-
|
107
|
-
assert_equal ast, parsed_ast,
|
108
|
-
"(#{version}) AST equality"
|
109
|
-
|
110
|
-
parse_source_map_descriptions(source_maps) do |range, map_field, ast_path, line|
|
111
|
-
|
112
|
-
astlet = traverse_ast(parsed_ast, ast_path)
|
113
|
-
|
114
|
-
if astlet.nil?
|
115
|
-
# This is a testsuite bug.
|
116
|
-
raise "No entity with AST path #{ast_path} in #{parsed_ast.inspect}"
|
117
|
-
end
|
118
|
-
|
119
|
-
assert astlet.frozen?
|
120
|
-
|
121
|
-
assert astlet.location.respond_to?(map_field),
|
122
|
-
"(#{version}) #{astlet.location.inspect}.respond_to?(#{map_field.inspect}) for:\n#{parsed_ast.inspect}"
|
123
|
-
|
124
|
-
found_range = astlet.location.send(map_field)
|
125
|
-
|
126
|
-
assert_source_range(range, found_range, version, line.inspect)
|
127
|
-
end
|
128
|
-
|
129
|
-
assert parser.instance_eval { @lexer }.cmdarg.empty?,
|
130
|
-
"(#{version}) expected cmdarg to be empty after parsing"
|
131
|
-
|
132
|
-
assert_equal 0, parser.instance_eval { @lexer.instance_eval { @paren_nest } },
|
133
|
-
"(#{version}) expected paren_nest to be 0 after parsing"
|
134
|
-
end
|
135
|
-
|
136
|
-
# Use like this:
|
137
|
-
# ~~~
|
138
|
-
# assert_diagnoses(
|
139
|
-
# [:warning, :ambiguous_prefix, { prefix: '*' }],
|
140
|
-
# %q{foo *bar},
|
141
|
-
# %q{ ^ location
|
142
|
-
# | ~~~ highlights (0)})
|
143
|
-
# ~~~
|
144
|
-
def assert_diagnoses(diagnostic, code, source_maps='', versions=ALL_VERSIONS)
|
145
|
-
with_versions(versions) do |version, parser|
|
146
|
-
source_file = Parser::Source::Buffer.new('(assert_diagnoses)', source: code)
|
147
|
-
|
148
|
-
begin
|
149
|
-
parser = parser.parse(source_file)
|
150
|
-
rescue Parser::SyntaxError
|
151
|
-
# do nothing; the diagnostic was reported
|
152
|
-
end
|
153
|
-
|
154
|
-
assert_equal 1, @diagnostics.count,
|
155
|
-
"(#{version}) emits a single diagnostic, not\n" \
|
156
|
-
"#{@diagnostics.map(&:render).join("\n")}"
|
157
|
-
|
158
|
-
emitted_diagnostic = @diagnostics.first
|
159
|
-
|
160
|
-
level, reason, arguments = diagnostic
|
161
|
-
arguments ||= {}
|
162
|
-
message = Parser::Messages.compile(reason, arguments)
|
163
|
-
|
164
|
-
assert_equal level, emitted_diagnostic.level
|
165
|
-
assert_equal reason, emitted_diagnostic.reason
|
166
|
-
assert_equal arguments, emitted_diagnostic.arguments
|
167
|
-
assert_equal message, emitted_diagnostic.message
|
168
|
-
|
169
|
-
parse_source_map_descriptions(source_maps) do |range, map_field, ast_path, line|
|
170
|
-
|
171
|
-
case map_field
|
172
|
-
when 'location'
|
173
|
-
assert_source_range range,
|
174
|
-
emitted_diagnostic.location,
|
175
|
-
version, 'location'
|
176
|
-
|
177
|
-
when 'highlights'
|
178
|
-
index = ast_path.first.to_i
|
179
|
-
|
180
|
-
assert_source_range range,
|
181
|
-
emitted_diagnostic.highlights[index],
|
182
|
-
version, "#{index}th highlight"
|
183
|
-
|
184
|
-
else
|
185
|
-
raise "Unknown diagnostic range #{map_field}"
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# Use like this:
|
192
|
-
# ~~~
|
193
|
-
# assert_diagnoses_many(
|
194
|
-
# [
|
195
|
-
# [:warning, :ambiguous_literal],
|
196
|
-
# [:error, :unexpected_token, { :token => :tLCURLY }]
|
197
|
-
# ],
|
198
|
-
# %q{m /foo/ {}},
|
199
|
-
# SINCE_2_4)
|
200
|
-
# ~~~
|
201
|
-
def assert_diagnoses_many(diagnostics, code, versions=ALL_VERSIONS)
|
202
|
-
with_versions(versions) do |version, parser|
|
203
|
-
source_file = Parser::Source::Buffer.new('(assert_diagnoses_many)', source: code)
|
204
|
-
|
205
|
-
begin
|
206
|
-
parser = parser.parse(source_file)
|
207
|
-
rescue Parser::SyntaxError
|
208
|
-
# do nothing; the diagnostic was reported
|
209
|
-
end
|
210
|
-
|
211
|
-
assert_equal diagnostics.count, @diagnostics.count
|
212
|
-
|
213
|
-
diagnostics.zip(@diagnostics) do |expected_diagnostic, actual_diagnostic|
|
214
|
-
level, reason, arguments = expected_diagnostic
|
215
|
-
arguments ||= {}
|
216
|
-
message = Parser::Messages.compile(reason, arguments)
|
217
|
-
|
218
|
-
assert_equal level, actual_diagnostic.level
|
219
|
-
assert_equal reason, actual_diagnostic.reason
|
220
|
-
assert_equal arguments, actual_diagnostic.arguments
|
221
|
-
assert_equal message, actual_diagnostic.message
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
|
226
|
-
def refute_diagnoses(code, versions=ALL_VERSIONS)
|
227
|
-
with_versions(versions) do |version, parser|
|
228
|
-
source_file = Parser::Source::Buffer.new('(refute_diagnoses)', 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)', source: code)
|
245
|
-
|
246
|
-
parsed_ast = parser.parse(source_file)
|
247
|
-
|
248
|
-
nodes = find_matching_nodes(parsed_ast) { |node| node.type == :send && node.children[1] == :get_context }
|
249
|
-
assert_equal 1, nodes.count, "there must exactly 1 `get_context()` call"
|
250
|
-
|
251
|
-
node = nodes.first
|
252
|
-
assert_equal context, node.context, "(#{version}) expect parsing context to match"
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
SOURCE_MAP_DESCRIPTION_RE =
|
257
|
-
/(?x)
|
258
|
-
^(?# $1 skip) ^(\s*)
|
259
|
-
(?# $2 highlight) ([~\^]+|\!)
|
260
|
-
\s+
|
261
|
-
(?# $3 source_map_field) ([a-z_]+)
|
262
|
-
(?# $5 ast_path) (\s+\(([a-z_.\/0-9]+)\))?
|
263
|
-
$/
|
264
|
-
|
265
|
-
def parse_source_map_descriptions(descriptions)
|
266
|
-
unless block_given?
|
267
|
-
return to_enum(:parse_source_map_descriptions, descriptions)
|
268
|
-
end
|
269
|
-
|
270
|
-
descriptions.each_line do |line|
|
271
|
-
# Remove leading " |", if it exists.
|
272
|
-
line = line.sub(/^\s*\|/, '').rstrip
|
273
|
-
|
274
|
-
next if line.empty?
|
275
|
-
|
276
|
-
if (match = SOURCE_MAP_DESCRIPTION_RE.match(line))
|
277
|
-
if match[2] != '!'
|
278
|
-
begin_pos = match[1].length
|
279
|
-
end_pos = begin_pos + match[2].length
|
280
|
-
range = begin_pos...end_pos
|
281
|
-
end
|
282
|
-
source_map_field = match[3]
|
283
|
-
|
284
|
-
if match[5]
|
285
|
-
ast_path = match[5].split('.')
|
286
|
-
else
|
287
|
-
ast_path = []
|
288
|
-
end
|
289
|
-
|
290
|
-
yield range, source_map_field, ast_path, line
|
291
|
-
else
|
292
|
-
raise "Cannot parse source map description line: #{line.inspect}."
|
293
|
-
end
|
294
|
-
end
|
295
|
-
end
|
296
|
-
|
297
|
-
def traverse_ast(ast, path)
|
298
|
-
path.inject(ast) do |astlet, path_component|
|
299
|
-
# Split "dstr/2" to :dstr and 1
|
300
|
-
type_str, index_str = path_component.split('/')
|
301
|
-
|
302
|
-
type = type_str.to_sym
|
303
|
-
|
304
|
-
if index_str.nil?
|
305
|
-
index = 0
|
306
|
-
else
|
307
|
-
index = index_str.to_i - 1
|
308
|
-
end
|
309
|
-
|
310
|
-
matching_children = \
|
311
|
-
astlet.children.select do |child|
|
312
|
-
AST::Node === child && child.type == type
|
313
|
-
end
|
314
|
-
|
315
|
-
matching_children[index]
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
def find_matching_nodes(ast, &block)
|
320
|
-
return [] unless ast.is_a?(AST::Node)
|
321
|
-
|
322
|
-
result = []
|
323
|
-
result << ast if block.call(ast)
|
324
|
-
ast.children.each { |child| result += find_matching_nodes(child, &block) }
|
325
|
-
|
326
|
-
result
|
327
|
-
end
|
328
|
-
end
|