ripper-plus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/LICENSE.txt +20 -0
- data/README.md +68 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/ripper-plus.rb +7 -0
- data/lib/ripper-plus/ripper-plus.rb +13 -0
- data/lib/ripper-plus/scope_stack.rb +54 -0
- data/lib/ripper-plus/transformer.rb +201 -0
- data/spec/ripper-plus_spec.rb +5 -0
- data/spec/scope_stack_spec.rb +29 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/transformer_spec.rb +467 -0
- metadata +132 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rspec", "~> 2.3.0"
|
10
|
+
gem "yard", "~> 0.6.0"
|
11
|
+
gem "bundler", "~> 1.0.0"
|
12
|
+
gem "jeweler", "~> 1.5.2"
|
13
|
+
gem "rcov", ">= 0"
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Michael Edgar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# ripper-plus
|
2
|
+
|
3
|
+
Ripper is the Ruby parser library packaged with Ruby 1.9. While quite complete, it has some quirks that can make use frustrating. This gem intends to correct them. The most important is bareword resolution in Ruby ASTs.
|
4
|
+
|
5
|
+
## Bareword Resolution
|
6
|
+
|
7
|
+
In Ruby, a local variable is created when it is assigned to. If Ruby finds a bareword `foo` before an assignment `foo = 5`, then Ruby will call `self.foo()`.
|
8
|
+
|
9
|
+
Kind of.
|
10
|
+
|
11
|
+
What happens when you run this code?
|
12
|
+
|
13
|
+
def label
|
14
|
+
'hello'
|
15
|
+
end
|
16
|
+
def print_label
|
17
|
+
label = 'Label: ' + label
|
18
|
+
puts label
|
19
|
+
end
|
20
|
+
print_label
|
21
|
+
|
22
|
+
If one takes the intuitive approach, one would assume it prints "Label: hello", right? Afraid not: it errors out. The intuitive approach says that `'Label: ' + label` is run before the assignment to the local variable `label`, so the `label` method will be called and appended to `"Label: "`. In reality, it won't, because local variables aren't created on assignment. Local variables are created *immediately when the Ruby parser parses a left-hand-side local variable*. Before the right-hand-side is even parsed, the local variable has been created. This also results in an infamous anomaly:
|
23
|
+
|
24
|
+
def say_what?
|
25
|
+
x = 'hello' unless defined?(x)
|
26
|
+
puts x
|
27
|
+
end
|
28
|
+
say_what?
|
29
|
+
|
30
|
+
`say_what?` prints a blank line! This is because as soon as the `x` on the LHS is parsed, `x` is a local variable with value `nil`. By the time `defined?(x)` is executed, `x` has long since been a local variable!
|
31
|
+
|
32
|
+
## Ripper's Mistake
|
33
|
+
|
34
|
+
Ripper doesn't carry scope information as it parses, and as such, parses any lone identifier as an AST node of type `:var_ref`. It is up to consumers of the AST to figure out the meaning of the `:var_ref` node. It can be reasonably argued that the semantics of the `:var_ref` node should not be part of the AST, as my thesis adviser pointed out when I complained about this, as the two are syntactically identical. Unfortunately, the meaning of the `var_ref` node comes from the parser itself; any attempt to determine semantics based solely on the AST is simply re-creating the work the parser ordinarily does! Indeed, when Ruby parses the code for execution, it *does* create different internal node types upon seeing a method-call bareword and a local variable bareword!
|
35
|
+
|
36
|
+
I'd like to see this behavior rolled into Ripper proper. Until then, `ripper-plus` is a reasonable replacement.
|
37
|
+
|
38
|
+
## ripper-plus
|
39
|
+
|
40
|
+
By using our knowledge of Ruby's parser, we can recreate the scope-tracking behavior by walking the AST generated by Ripper. We simply must do normal scope tracking, observe the creation of local variables, and ensure that we walk each node of the tree in the order in which those nodes would have been parsed. Most subtrees generated by Ripper are already in this order, with the exception of the modifier nodes (`foo.bar if baz`, `x = bar() while x != nil`). Most importantly, since everything is an expression in Ruby and local variable assignments can occur just about anywhere, exact scoping semantics must be recreated. Corner cases such as this:
|
41
|
+
|
42
|
+
def foo(x, y = y)
|
43
|
+
end
|
44
|
+
|
45
|
+
Need to be properly resolved. Did you know that, unlike the `label = label` example above, the above code is equivalent to:
|
46
|
+
|
47
|
+
def foo(x, y = y())
|
48
|
+
end
|
49
|
+
|
50
|
+
Anyway, ripper-plus turns all method-call `:var_ref` nodes into `zcall` nodes; the node structure is otherwise unchanged.
|
51
|
+
|
52
|
+
The truth is, everybody who is using Ripper right now *should* be doing *all* of this. Anything short, and you have bugs. [Laser](https://github.com/michaeledgar/laser/) has bugs as a result. It's a pain in the ass to get it all right. So hopefully, in Ruby 1.9.x, this will be the default. For now, you *should* use ripper-plus.
|
53
|
+
|
54
|
+
## Contributing to ripper-plus
|
55
|
+
|
56
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
57
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
58
|
+
* Fork the project
|
59
|
+
* Start a feature/bugfix branch
|
60
|
+
* Commit and push until you are happy with your contribution
|
61
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
62
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
63
|
+
|
64
|
+
## Copyright
|
65
|
+
|
66
|
+
Copyright (c) 2011 Michael Edgar. See LICENSE.txt for
|
67
|
+
further details.
|
68
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "ripper-plus"
|
16
|
+
gem.homepage = "http://github.com/michaeledgar/ripper-plus"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{Parses Ruby code into an improved Ripper AST format}
|
19
|
+
gem.description = %Q{Ripper is the Ruby parser library packaged
|
20
|
+
with Ruby 1.9. While quite complete, it has some quirks that can
|
21
|
+
make use frustrating. This gem intends to correct them.}
|
22
|
+
gem.email = "michael.j.edgar@dartmouth.edu"
|
23
|
+
gem.authors = ["Michael Edgar"]
|
24
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
25
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
26
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
27
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
28
|
+
end
|
29
|
+
Jeweler::RubygemsDotOrgTasks.new
|
30
|
+
|
31
|
+
require 'rspec/core'
|
32
|
+
require 'rspec/core/rake_task'
|
33
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
34
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
35
|
+
end
|
36
|
+
|
37
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
38
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
39
|
+
spec.rcov = true
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :spec
|
43
|
+
|
44
|
+
require 'yard'
|
45
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/ripper-plus.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Top-level module for Ripper Plus. Provides global methods for
|
2
|
+
# getting a RipperPlus AST for a given input program.
|
3
|
+
module RipperPlus
|
4
|
+
# Parses the given Ruby code into a RipperPlus AST.
|
5
|
+
def self.sexp(text)
|
6
|
+
for_ripper_ast(Ripper.sexp(text))
|
7
|
+
end
|
8
|
+
|
9
|
+
# Transforms the provided Ripper AST into a RipperPlus AST.
|
10
|
+
def self.for_ripper_ast(tree)
|
11
|
+
Transformer.transform(tree)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RipperPlus
|
2
|
+
# internal class that manages the current scopes.
|
3
|
+
class ScopeStack
|
4
|
+
SCOPE_BLOCKER_9000 = :scope_block
|
5
|
+
def initialize
|
6
|
+
# simplifies algorithm to have the scope blocker be the stack base
|
7
|
+
@stack = [SCOPE_BLOCKER_9000, Set.new]
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
middle = @stack.map do |scope|
|
12
|
+
if SCOPE_BLOCKER_9000 == scope
|
13
|
+
'||'
|
14
|
+
else
|
15
|
+
"[ #{scope.to_a.sort.join(', ')} ]"
|
16
|
+
end
|
17
|
+
end.join(' ')
|
18
|
+
"< #{middle} >"
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_variable(var)
|
22
|
+
@stack.last << var
|
23
|
+
end
|
24
|
+
|
25
|
+
# An open scope permits reference to local variables in enclosing scopes.
|
26
|
+
def with_open_scope
|
27
|
+
@stack.push(Set.new)
|
28
|
+
yield
|
29
|
+
ensure
|
30
|
+
@stack.pop # pops open scope
|
31
|
+
end
|
32
|
+
|
33
|
+
# An open scope denies reference to local variables in enclosing scopes.
|
34
|
+
def with_closed_scope
|
35
|
+
@stack.push(SCOPE_BLOCKER_9000)
|
36
|
+
@stack.push(Set.new)
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
@stack.pop # pop closed scope
|
40
|
+
@stack.pop # pop scope blocker
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks if the given variable is in scope.
|
44
|
+
def has_variable?(var)
|
45
|
+
@stack.reverse_each do |scope|
|
46
|
+
if SCOPE_BLOCKER_9000 == scope
|
47
|
+
return false
|
48
|
+
elsif scope.include?(var)
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'set'
|
2
|
+
module RipperPlus
|
3
|
+
# Transforms a 1.9.2 Ripper AST into a RipperPlus AST. The only
|
4
|
+
# change as a result of this transformation is that the nodes for
|
5
|
+
# local variable references and zcalls (bareword calls to self)
|
6
|
+
# have different node types:
|
7
|
+
#
|
8
|
+
# def foo(x)
|
9
|
+
# y
|
10
|
+
# y = x
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# becomes
|
14
|
+
#
|
15
|
+
# [:def,
|
16
|
+
# [:@ident, "foo", [1, 4]],
|
17
|
+
# [:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
|
18
|
+
# [:bodystmt,
|
19
|
+
# [[:zcall, [:@ident, "y", [2, 2]]],
|
20
|
+
# [:assign,
|
21
|
+
# [:var_field, [:@ident, "y", [3, 2]]],
|
22
|
+
# [:var_ref, [:@ident, "x", [3, 6]]]]],
|
23
|
+
# nil, nil, nil]]
|
24
|
+
#
|
25
|
+
# while:
|
26
|
+
#
|
27
|
+
# def foo(x)
|
28
|
+
# y = x
|
29
|
+
# y
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# becomes
|
33
|
+
#
|
34
|
+
# [:def,
|
35
|
+
# [:@ident, "foo", [1, 4]],
|
36
|
+
# [:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
|
37
|
+
# [:bodystmt,
|
38
|
+
# [[:assign,
|
39
|
+
# [:var_field, [:@ident, "y", [2, 2]]],
|
40
|
+
# [:var_ref, [:@ident, "x", [2, 6]]]],
|
41
|
+
# [:var_ref, [:@ident, "y", [3, 2]]]],
|
42
|
+
# nil, nil, nil]]
|
43
|
+
module Transformer
|
44
|
+
extend self
|
45
|
+
|
46
|
+
# Transforms the given AST into a RipperPlus AST.
|
47
|
+
def transform(root)
|
48
|
+
new_copy = clone_sexp(root)
|
49
|
+
scope_stack = ScopeStack.new
|
50
|
+
transform_tree(new_copy, scope_stack)
|
51
|
+
new_copy
|
52
|
+
end
|
53
|
+
|
54
|
+
# Transforms the given tree into a RipperPlus AST.
|
55
|
+
def transform_tree(tree, scope_stack)
|
56
|
+
case tree[0]
|
57
|
+
when :assign, :massign
|
58
|
+
lhs, rhs = tree[1..2]
|
59
|
+
add_variables_from_lhs(lhs, scope_stack)
|
60
|
+
transform_tree(rhs, scope_stack)
|
61
|
+
when :for
|
62
|
+
vars, iterated, body = tree[1..3]
|
63
|
+
add_variables_from_lhs(vars, scope_stack)
|
64
|
+
transform_tree(iterated, scope_stack)
|
65
|
+
transform_tree(body, scope_stack)
|
66
|
+
when :var_ref
|
67
|
+
if tree[1][0] == :@ident && !scope_stack.has_variable?(tree[1][1])
|
68
|
+
tree[0] = :zcall
|
69
|
+
end
|
70
|
+
when :class
|
71
|
+
superclass, body = tree[2..3]
|
72
|
+
transform_tree(superclass, scope_stack) if superclass # superclass node
|
73
|
+
scope_stack.with_closed_scope do
|
74
|
+
transform_tree(body, scope_stack)
|
75
|
+
end
|
76
|
+
when :module
|
77
|
+
scope_stack.with_closed_scope do
|
78
|
+
transform_tree(tree[2], scope_stack) # body
|
79
|
+
end
|
80
|
+
when :sclass
|
81
|
+
singleton, body = tree[1..2]
|
82
|
+
transform_tree(singleton, scope_stack)
|
83
|
+
scope_stack.with_closed_scope do
|
84
|
+
transform_tree(body, scope_stack)
|
85
|
+
end
|
86
|
+
when :def
|
87
|
+
scope_stack.with_closed_scope do
|
88
|
+
param_node = tree[2]
|
89
|
+
body = tree[3]
|
90
|
+
transform_params(param_node, scope_stack)
|
91
|
+
transform_tree(body, scope_stack)
|
92
|
+
end
|
93
|
+
when :defs
|
94
|
+
transform_tree(tree[1], scope_stack) # singleton could be a method call!
|
95
|
+
scope_stack.with_closed_scope do
|
96
|
+
param_node = tree[4]
|
97
|
+
body = tree[5]
|
98
|
+
transform_params(param_node, scope_stack)
|
99
|
+
transform_tree(body, scope_stack)
|
100
|
+
end
|
101
|
+
when :method_add_block
|
102
|
+
call, block = tree[1..2]
|
103
|
+
# first transform the call
|
104
|
+
transform_tree(call, scope_stack)
|
105
|
+
# then transform the block
|
106
|
+
block_args, block_body = block[1..2]
|
107
|
+
scope_stack.with_open_scope do
|
108
|
+
if block_args
|
109
|
+
transform_params(block_args[1], scope_stack)
|
110
|
+
if block_args[2]
|
111
|
+
block_args[2].each { |var| add_variables_from_lhs(var, scope_stack) }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
transform_tree(block_body, scope_stack)
|
115
|
+
end
|
116
|
+
when :if_mod, :unless_mod, :while_mod, :until_mod, :rescue_mod
|
117
|
+
# The AST is the reverse of the parse order for these nodes.
|
118
|
+
transform_tree(tree[2], scope_stack)
|
119
|
+
transform_tree(tree[1], scope_stack)
|
120
|
+
else
|
121
|
+
transform_in_order(tree, scope_stack)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def add_variables_from_lhs(lhs, scope_stack)
|
126
|
+
case lhs[0]
|
127
|
+
when :@ident
|
128
|
+
scope_stack.add_variable(lhs[1])
|
129
|
+
when Array
|
130
|
+
lhs.each { |var| add_variables_from_lhs(var, scope_stack) }
|
131
|
+
when :mlhs_paren, :var_field, :rest_param, :blockarg
|
132
|
+
add_variables_from_lhs(lhs[1], scope_stack)
|
133
|
+
when :mlhs_add_star
|
134
|
+
pre_star, star, post_star = lhs[1..3]
|
135
|
+
pre_star.each { |var| add_variables_from_lhs(var, scope_stack) }
|
136
|
+
if star
|
137
|
+
add_variables_from_lhs(star, scope_stack)
|
138
|
+
end
|
139
|
+
if post_star
|
140
|
+
post_star.each { |var| add_variables_from_lhs(var, scope_stack) }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# If this node's subtrees are ordered as they are lexically, as most are,
|
146
|
+
# transform each subtree in order.
|
147
|
+
def transform_in_order(tree, scope_stack)
|
148
|
+
# some nodes have no type: include the first element in this case
|
149
|
+
range = Symbol === tree[0] ? 1..-1 : 0..-1
|
150
|
+
tree[range].each do |subtree|
|
151
|
+
# obviously don't transform literals or token locations
|
152
|
+
if Array === subtree && !(Fixnum === subtree[0])
|
153
|
+
transform_tree(subtree, scope_stack)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Transforms a parameter list, and adds the new variables to current scope.
|
159
|
+
# Used by both block args and method args.
|
160
|
+
def transform_params(param_node, scope_stack)
|
161
|
+
param_node = param_node[1] if param_node[0] == :paren
|
162
|
+
if param_node
|
163
|
+
positional_1, optional, rest, positional_2, block = param_node[1..5]
|
164
|
+
if positional_1
|
165
|
+
positional_1.each { |var| add_variables_from_lhs(var, scope_stack) }
|
166
|
+
end
|
167
|
+
if optional
|
168
|
+
optional.each do |var, value|
|
169
|
+
# MUST walk value first. (def foo(y=y); end) == (def foo(y=y()); end)
|
170
|
+
transform_tree(value, scope_stack)
|
171
|
+
add_variables_from_lhs(var, scope_stack)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
if rest && rest[1]
|
175
|
+
add_variables_from_lhs(rest, scope_stack)
|
176
|
+
end
|
177
|
+
if positional_2
|
178
|
+
positional_2.each { |var| add_variables_from_lhs(var, scope_stack) }
|
179
|
+
end
|
180
|
+
if block
|
181
|
+
add_variables_from_lhs(block, scope_stack)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Deep-copies the sexp. I wish Array#clone did deep copies...
|
187
|
+
def clone_sexp(node)
|
188
|
+
node.map do |part|
|
189
|
+
# guess: arrays most common, then un-dupables, then dup-ables (strings only)
|
190
|
+
if Array === part
|
191
|
+
clone_sexp(part)
|
192
|
+
elsif [Symbol, Fixnum, TrueClass, FalseClass, NilClass].any? { |k| k === part }
|
193
|
+
part
|
194
|
+
else
|
195
|
+
part.dup
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
private :clone_sexp
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe RipperPlus::ScopeStack do
|
4
|
+
before do
|
5
|
+
@stack = RipperPlus::ScopeStack.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'can see variables added at top level' do
|
9
|
+
@stack.should_not have_variable(:hello)
|
10
|
+
@stack.add_variable :hello
|
11
|
+
@stack.should have_variable(:hello)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can not see variables added beyond a closed scope' do
|
15
|
+
@stack.add_variable :hello
|
16
|
+
@stack.with_closed_scope do
|
17
|
+
@stack.should_not have_variable(:hello)
|
18
|
+
end
|
19
|
+
@stack.should have_variable(:hello)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can see variables added beyond an open scope' do
|
23
|
+
@stack.add_variable :hello
|
24
|
+
@stack.with_open_scope do
|
25
|
+
@stack.should have_variable(:hello)
|
26
|
+
end
|
27
|
+
@stack.should have_variable(:hello)
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'ripper-plus'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec::Matchers.define :transform_to do |output|
|
11
|
+
match do |input|
|
12
|
+
RipperPlus::Transformer.transform(input) == output
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,467 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe RipperPlus::Transformer do
|
4
|
+
it 'should transform a simple zcall in a method' do
|
5
|
+
input_tree =
|
6
|
+
[:program,
|
7
|
+
[[:def,
|
8
|
+
[:@ident, "foo", [1, 4]],
|
9
|
+
[:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
|
10
|
+
[:bodystmt,
|
11
|
+
[[:void_stmt],
|
12
|
+
[:var_ref, [:@ident, "y", [1, 12]]],
|
13
|
+
[:assign,
|
14
|
+
[:var_field, [:@ident, "y", [1, 15]]],
|
15
|
+
[:var_ref, [:@ident, "x", [1, 19]]]]],
|
16
|
+
nil, nil, nil]]]]
|
17
|
+
output_tree =
|
18
|
+
[:program,
|
19
|
+
[[:def,
|
20
|
+
[:@ident, "foo", [1, 4]],
|
21
|
+
[:paren, [:params, [[:@ident, "x", [1, 8]]], nil, nil, nil, nil]],
|
22
|
+
[:bodystmt,
|
23
|
+
[[:void_stmt],
|
24
|
+
[:zcall, [:@ident, "y", [1, 12]]],
|
25
|
+
[:assign,
|
26
|
+
[:var_field, [:@ident, "y", [1, 15]]],
|
27
|
+
[:var_ref, [:@ident, "x", [1, 19]]]]],
|
28
|
+
nil, nil, nil]]]]
|
29
|
+
input_tree.should transform_to(output_tree)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should respect argument order in method definitions' do
|
33
|
+
input_tree =
|
34
|
+
[:program,
|
35
|
+
[[:def,
|
36
|
+
[:@ident, "foo", [1, 4]],
|
37
|
+
[:paren,
|
38
|
+
[:params,
|
39
|
+
[[:@ident, "x", [1, 8]]],
|
40
|
+
[[[:@ident, "y", [1, 11]], [:var_ref, [:@ident, "z", [1, 13]]]],
|
41
|
+
[[:@ident, "z", [1, 16]], [:var_ref, [:@ident, "y", [1, 18]]]]],
|
42
|
+
nil,
|
43
|
+
nil,
|
44
|
+
nil]],
|
45
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
46
|
+
output_tree =
|
47
|
+
[:program,
|
48
|
+
[[:def,
|
49
|
+
[:@ident, "foo", [1, 4]],
|
50
|
+
[:paren,
|
51
|
+
[:params,
|
52
|
+
[[:@ident, "x", [1, 8]]],
|
53
|
+
[[[:@ident, "y", [1, 11]], [:zcall, [:@ident, "z", [1, 13]]]],
|
54
|
+
[[:@ident, "z", [1, 16]], [:var_ref, [:@ident, "y", [1, 18]]]]],
|
55
|
+
nil,
|
56
|
+
nil,
|
57
|
+
nil]],
|
58
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
59
|
+
input_tree.should transform_to(output_tree)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should respect argument order in block argument definitions' do
|
63
|
+
input_tree =
|
64
|
+
[:program,
|
65
|
+
[[:method_add_block,
|
66
|
+
[:zsuper],
|
67
|
+
[:do_block,
|
68
|
+
[:block_var,
|
69
|
+
[:params,
|
70
|
+
[[:@ident, "x", [1, 10]]],
|
71
|
+
[[[:@ident, "y", [1, 13]], [:var_ref, [:@ident, "x", [1, 15]]]],
|
72
|
+
[[:@ident, "z", [1, 18]], [:var_ref, [:@ident, "a", [1, 20]]]]],
|
73
|
+
[:rest_param, [:@ident, "a", [1, 24]]],
|
74
|
+
nil,
|
75
|
+
nil],
|
76
|
+
nil],
|
77
|
+
[[:void_stmt],
|
78
|
+
[:command,
|
79
|
+
[:@ident, "p", [1, 28]],
|
80
|
+
[:args_add_block,
|
81
|
+
[[:var_ref, [:@ident, "x", [1, 30]]],
|
82
|
+
[:var_ref, [:@ident, "y", [1, 33]]],
|
83
|
+
[:var_ref, [:@ident, "z", [1, 36]]],
|
84
|
+
[:var_ref, [:@ident, "a", [1, 39]]]],
|
85
|
+
false]]]]]]]
|
86
|
+
output_tree =
|
87
|
+
[:program,
|
88
|
+
[[:method_add_block,
|
89
|
+
[:zsuper],
|
90
|
+
[:do_block,
|
91
|
+
[:block_var,
|
92
|
+
[:params,
|
93
|
+
[[:@ident, "x", [1, 10]]],
|
94
|
+
[[[:@ident, "y", [1, 13]], [:var_ref, [:@ident, "x", [1, 15]]]],
|
95
|
+
[[:@ident, "z", [1, 18]], [:zcall, [:@ident, "a", [1, 20]]]]],
|
96
|
+
[:rest_param, [:@ident, "a", [1, 24]]],
|
97
|
+
nil,
|
98
|
+
nil],
|
99
|
+
nil],
|
100
|
+
[[:void_stmt],
|
101
|
+
[:command,
|
102
|
+
[:@ident, "p", [1, 28]],
|
103
|
+
[:args_add_block,
|
104
|
+
[[:var_ref, [:@ident, "x", [1, 30]]],
|
105
|
+
[:var_ref, [:@ident, "y", [1, 33]]],
|
106
|
+
[:var_ref, [:@ident, "z", [1, 36]]],
|
107
|
+
[:var_ref, [:@ident, "a", [1, 39]]]],
|
108
|
+
false]]]]]]]
|
109
|
+
input_tree.should transform_to(output_tree)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should transform singleton names in singleton method definitions' do
|
113
|
+
input_tree =
|
114
|
+
[:program,
|
115
|
+
[[:defs,
|
116
|
+
[:var_ref, [:@ident, "foo", [1, 4]]],
|
117
|
+
[:@period, ".", [1, 7]],
|
118
|
+
[:@ident, "silly", [1, 8]],
|
119
|
+
[:paren, [:params, nil, nil, nil, nil, nil]],
|
120
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
121
|
+
output_tree =
|
122
|
+
[:program,
|
123
|
+
[[:defs,
|
124
|
+
[:zcall, [:@ident, "foo", [1, 4]]],
|
125
|
+
[:@period, ".", [1, 7]],
|
126
|
+
[:@ident, "silly", [1, 8]],
|
127
|
+
[:paren, [:params, nil, nil, nil, nil, nil]],
|
128
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
129
|
+
input_tree.should transform_to(output_tree)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should not transform singleton names when an appropriate local variable exists' do
|
133
|
+
input_tree =
|
134
|
+
[:program,
|
135
|
+
[[:assign,
|
136
|
+
[:var_field, [:@ident, "foo", [1, 0]]],
|
137
|
+
[:var_ref, [:@kw, "self", [1, 6]]]],
|
138
|
+
[:defs,
|
139
|
+
[:var_ref, [:@ident, "foo", [1, 16]]],
|
140
|
+
[:@period, ".", [1, 19]],
|
141
|
+
[:@ident, "silly", [1, 20]],
|
142
|
+
[:params, nil, nil, nil, nil, nil],
|
143
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
144
|
+
input_tree.should transform_to(input_tree)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'does not transform the tricky x = 5 unless defined?(x) case' do
|
148
|
+
input_tree =
|
149
|
+
[:program,
|
150
|
+
[[:unless_mod,
|
151
|
+
[:defined, [:var_ref, [:@ident, "x", [1, 22]]]],
|
152
|
+
[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]]]]]
|
153
|
+
input_tree.should transform_to(input_tree)
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'finds all LHS vars before transforming an MLHS' do
|
157
|
+
input_tree =
|
158
|
+
[:program,
|
159
|
+
[[:massign,
|
160
|
+
[[:@ident, "a", [1, 0]],
|
161
|
+
[:@const, "B", [1, 3]],
|
162
|
+
[:mlhs_paren,
|
163
|
+
[:mlhs_add_star,
|
164
|
+
[[:@gvar, "$c", [1, 7]]],
|
165
|
+
[:@ident, "rest", [1, 12]],
|
166
|
+
[[:@ident, "d", [1, 18]], [:@ident, "e", [1, 21]]]]]],
|
167
|
+
[:method_add_arg,
|
168
|
+
[:fcall, [:@ident, "foo", [1, 26]]],
|
169
|
+
[:arg_paren,
|
170
|
+
[:args_add_block,
|
171
|
+
[[:var_ref, [:@ident, "a", [1, 30]]],
|
172
|
+
[:var_ref, [:@ident, "c", [1, 33]]],
|
173
|
+
[:var_ref, [:@ident, "d", [1, 36]]]],
|
174
|
+
false]]]]]]
|
175
|
+
output_tree =
|
176
|
+
[:program,
|
177
|
+
[[:massign,
|
178
|
+
[[:@ident, "a", [1, 0]],
|
179
|
+
[:@const, "B", [1, 3]],
|
180
|
+
[:mlhs_paren,
|
181
|
+
[:mlhs_add_star,
|
182
|
+
[[:@gvar, "$c", [1, 7]]],
|
183
|
+
[:@ident, "rest", [1, 12]],
|
184
|
+
[[:@ident, "d", [1, 18]], [:@ident, "e", [1, 21]]]]]],
|
185
|
+
[:method_add_arg,
|
186
|
+
[:fcall, [:@ident, "foo", [1, 26]]],
|
187
|
+
[:arg_paren,
|
188
|
+
[:args_add_block,
|
189
|
+
[[:var_ref, [:@ident, "a", [1, 30]]],
|
190
|
+
[:zcall, [:@ident, "c", [1, 33]]],
|
191
|
+
[:var_ref, [:@ident, "d", [1, 36]]]],
|
192
|
+
false]]]]]]
|
193
|
+
input_tree.should transform_to(output_tree)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'creates for-loop MLHS vars before transforming the iteratee' do
|
197
|
+
input_tree =
|
198
|
+
[:program,
|
199
|
+
[[:for,
|
200
|
+
[[:@ident, "a", [1, 4]],
|
201
|
+
[:@ident, "b", [1, 7]],
|
202
|
+
[:mlhs_paren,
|
203
|
+
[[:@ident, "c", [1, 11]],
|
204
|
+
[:mlhs_paren,
|
205
|
+
[:mlhs_add_star,
|
206
|
+
[],
|
207
|
+
[:@ident, "d", [1, 16]],
|
208
|
+
[[:@ident, "e", [1, 19]]]]]]]],
|
209
|
+
[:method_add_arg,
|
210
|
+
[:fcall, [:@ident, "foo", [1, 26]]],
|
211
|
+
[:arg_paren,
|
212
|
+
[:args_add_block,
|
213
|
+
[[:var_ref, [:@ident, "a", [1, 30]]],
|
214
|
+
[:var_ref, [:@ident, "b", [1, 33]]],
|
215
|
+
[:var_ref, [:@ident, "c", [1, 36]]],
|
216
|
+
[:var_ref, [:@ident, "d", [1, 39]]],
|
217
|
+
[:var_ref, [:@ident, "e", [1, 42]]],
|
218
|
+
[:var_ref, [:@ident, "f", [1, 45]]],
|
219
|
+
[:var_ref, [:@ident, "g", [1, 48]]]],
|
220
|
+
false]]],
|
221
|
+
[[:void_stmt]]]]]
|
222
|
+
output_tree =
|
223
|
+
[:program,
|
224
|
+
[[:for,
|
225
|
+
[[:@ident, "a", [1, 4]],
|
226
|
+
[:@ident, "b", [1, 7]],
|
227
|
+
[:mlhs_paren,
|
228
|
+
[[:@ident, "c", [1, 11]],
|
229
|
+
[:mlhs_paren,
|
230
|
+
[:mlhs_add_star,
|
231
|
+
[],
|
232
|
+
[:@ident, "d", [1, 16]],
|
233
|
+
[[:@ident, "e", [1, 19]]]]]]]],
|
234
|
+
[:method_add_arg,
|
235
|
+
[:fcall, [:@ident, "foo", [1, 26]]],
|
236
|
+
[:arg_paren,
|
237
|
+
[:args_add_block,
|
238
|
+
[[:var_ref, [:@ident, "a", [1, 30]]],
|
239
|
+
[:var_ref, [:@ident, "b", [1, 33]]],
|
240
|
+
[:var_ref, [:@ident, "c", [1, 36]]],
|
241
|
+
[:var_ref, [:@ident, "d", [1, 39]]],
|
242
|
+
[:var_ref, [:@ident, "e", [1, 42]]],
|
243
|
+
[:zcall, [:@ident, "f", [1, 45]]],
|
244
|
+
[:zcall, [:@ident, "g", [1, 48]]]],
|
245
|
+
false]]],
|
246
|
+
[[:void_stmt]]]]]
|
247
|
+
input_tree.should transform_to(output_tree)
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'should transform respecting subassignments in block arguments' do
|
251
|
+
input_tree =
|
252
|
+
[:program,
|
253
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "10", [1, 4]]],
|
254
|
+
[:method_add_block,
|
255
|
+
[:call,
|
256
|
+
[:top_const_ref, [:@const, "ARR", [1, 10]]],
|
257
|
+
:".",
|
258
|
+
[:@ident, "each", [1, 14]]],
|
259
|
+
[:brace_block,
|
260
|
+
[:block_var,
|
261
|
+
[:params,
|
262
|
+
[[:@ident, "y", [1, 22]],
|
263
|
+
[:mlhs_paren,
|
264
|
+
[[:mlhs_paren, [:@ident, "z", [1, 26]]],
|
265
|
+
[:mlhs_paren, [:@ident, "a", [1, 29]]]]]],
|
266
|
+
[[[:@ident, "k", [1, 33]], [:var_ref, [:@ident, "z", [1, 37]]]],
|
267
|
+
[[:@ident, "j", [1, 40]], [:var_ref, [:@ident, "d", [1, 44]]]]],
|
268
|
+
[:rest_param, [:@ident, "r", [1, 48]]],
|
269
|
+
nil,
|
270
|
+
[:blockarg, [:@ident, "blk", [1, 52]]]],
|
271
|
+
[[:@ident, "local", [1, 57]]]],
|
272
|
+
[[:command,
|
273
|
+
[:@ident, "p", [1, 64]],
|
274
|
+
[:args_add_block,
|
275
|
+
[[:var_ref, [:@ident, "x", [1, 66]]],
|
276
|
+
[:var_ref, [:@ident, "y", [1, 69]]],
|
277
|
+
[:var_ref, [:@ident, "z", [1, 72]]],
|
278
|
+
[:var_ref, [:@ident, "a", [1, 75]]],
|
279
|
+
[:var_ref, [:@ident, "k", [1, 78]]],
|
280
|
+
[:var_ref, [:@ident, "j", [1, 81]]],
|
281
|
+
[:var_ref, [:@ident, "r", [1, 84]]],
|
282
|
+
[:var_ref, [:@ident, "blk", [1, 87]]],
|
283
|
+
[:var_ref, [:@ident, "local", [1, 92]]],
|
284
|
+
[:var_ref, [:@ident, "foo", [1, 99]]]],
|
285
|
+
false]]]]]]]
|
286
|
+
output_tree =
|
287
|
+
[:program,
|
288
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "10", [1, 4]]],
|
289
|
+
[:method_add_block,
|
290
|
+
[:call,
|
291
|
+
[:top_const_ref, [:@const, "ARR", [1, 10]]],
|
292
|
+
:".",
|
293
|
+
[:@ident, "each", [1, 14]]],
|
294
|
+
[:brace_block,
|
295
|
+
[:block_var,
|
296
|
+
[:params,
|
297
|
+
[[:@ident, "y", [1, 22]],
|
298
|
+
[:mlhs_paren,
|
299
|
+
[[:mlhs_paren, [:@ident, "z", [1, 26]]],
|
300
|
+
[:mlhs_paren, [:@ident, "a", [1, 29]]]]]],
|
301
|
+
[[[:@ident, "k", [1, 33]], [:var_ref, [:@ident, "z", [1, 37]]]],
|
302
|
+
[[:@ident, "j", [1, 40]], [:zcall, [:@ident, "d", [1, 44]]]]],
|
303
|
+
[:rest_param, [:@ident, "r", [1, 48]]],
|
304
|
+
nil,
|
305
|
+
[:blockarg, [:@ident, "blk", [1, 52]]]],
|
306
|
+
[[:@ident, "local", [1, 57]]]],
|
307
|
+
[[:command,
|
308
|
+
[:@ident, "p", [1, 64]],
|
309
|
+
[:args_add_block,
|
310
|
+
[[:var_ref, [:@ident, "x", [1, 66]]],
|
311
|
+
[:var_ref, [:@ident, "y", [1, 69]]],
|
312
|
+
[:var_ref, [:@ident, "z", [1, 72]]],
|
313
|
+
[:var_ref, [:@ident, "a", [1, 75]]],
|
314
|
+
[:var_ref, [:@ident, "k", [1, 78]]],
|
315
|
+
[:var_ref, [:@ident, "j", [1, 81]]],
|
316
|
+
[:var_ref, [:@ident, "r", [1, 84]]],
|
317
|
+
[:var_ref, [:@ident, "blk", [1, 87]]],
|
318
|
+
[:var_ref, [:@ident, "local", [1, 92]]],
|
319
|
+
[:zcall, [:@ident, "foo", [1, 99]]]],
|
320
|
+
false]]]]]]]
|
321
|
+
input_tree.should transform_to(output_tree)
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'does not let block variables escape into enclosing scopes' do
|
325
|
+
input_tree =
|
326
|
+
[:program,
|
327
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
328
|
+
[:method_add_block,
|
329
|
+
[:call, [:array, nil], :".", [:@ident, "each", [1, 10]]],
|
330
|
+
[:brace_block,
|
331
|
+
[:block_var,
|
332
|
+
[:params, [[:@ident, "y", [1, 18]]], nil, nil, nil, nil],
|
333
|
+
[[:@ident, "z", [1, 20]], [:@ident, "x", [1, 23]]]],
|
334
|
+
[[:var_ref, [:@ident, "x", [1, 26]]],
|
335
|
+
[:var_ref, [:@ident, "y", [1, 28]]],
|
336
|
+
[:var_ref, [:@ident, "z", [1, 30]]]]]],
|
337
|
+
[:var_ref, [:@ident, "x", [1, 36]]],
|
338
|
+
[:var_ref, [:@ident, "y", [1, 39]]],
|
339
|
+
[:var_ref, [:@ident, "z", [1, 42]]]]]
|
340
|
+
output_tree =
|
341
|
+
[:program,
|
342
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
343
|
+
[:method_add_block,
|
344
|
+
[:call, [:array, nil], :".", [:@ident, "each", [1, 10]]],
|
345
|
+
[:brace_block,
|
346
|
+
[:block_var,
|
347
|
+
[:params, [[:@ident, "y", [1, 18]]], nil, nil, nil, nil],
|
348
|
+
[[:@ident, "z", [1, 20]], [:@ident, "x", [1, 23]]]],
|
349
|
+
[[:var_ref, [:@ident, "x", [1, 26]]],
|
350
|
+
[:var_ref, [:@ident, "y", [1, 28]]],
|
351
|
+
[:var_ref, [:@ident, "z", [1, 30]]]]]],
|
352
|
+
[:var_ref, [:@ident, "x", [1, 36]]],
|
353
|
+
[:zcall, [:@ident, "y", [1, 39]]],
|
354
|
+
[:zcall, [:@ident, "z", [1, 42]]]]]
|
355
|
+
input_tree.should transform_to(output_tree)
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'creates closed scopes when classes are created' do
|
359
|
+
input_tree =
|
360
|
+
[:program,
|
361
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
362
|
+
[:class,
|
363
|
+
[:const_ref, [:@const, "Foo", [1, 13]]],
|
364
|
+
nil,
|
365
|
+
[:bodystmt, [[:var_ref, [:@ident, "x", [1, 18]]]], nil, nil, nil]],
|
366
|
+
[:var_ref, [:@ident, "x", [1, 26]]]]]
|
367
|
+
output_tree =
|
368
|
+
[:program,
|
369
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
370
|
+
[:class,
|
371
|
+
[:const_ref, [:@const, "Foo", [1, 13]]],
|
372
|
+
nil,
|
373
|
+
[:bodystmt, [[:zcall, [:@ident, "x", [1, 18]]]], nil, nil, nil]],
|
374
|
+
[:var_ref, [:@ident, "x", [1, 26]]]]]
|
375
|
+
input_tree.should transform_to(output_tree)
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'uses the previous scope for superclass specification' do
|
379
|
+
input_tree =
|
380
|
+
[:program,
|
381
|
+
[[:assign,
|
382
|
+
[:var_field, [:@ident, "x", [1, 0]]],
|
383
|
+
[:var_ref, [:@const, "String", [1, 4]]]],
|
384
|
+
[:class,
|
385
|
+
[:const_ref, [:@const, "A", [1, 18]]],
|
386
|
+
[:var_ref, [:@ident, "x", [1, 22]]],
|
387
|
+
[:bodystmt, [[:var_ref, [:@ident, "x", [1, 25]]]], nil, nil, nil]]]]
|
388
|
+
output_tree =
|
389
|
+
[:program,
|
390
|
+
[[:assign,
|
391
|
+
[:var_field, [:@ident, "x", [1, 0]]],
|
392
|
+
[:var_ref, [:@const, "String", [1, 4]]]],
|
393
|
+
[:class,
|
394
|
+
[:const_ref, [:@const, "A", [1, 18]]],
|
395
|
+
[:var_ref, [:@ident, "x", [1, 22]]],
|
396
|
+
[:bodystmt, [[:zcall, [:@ident, "x", [1, 25]]]], nil, nil, nil]]]]
|
397
|
+
input_tree.should transform_to output_tree
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'creates closed scopes when modules are created' do
|
401
|
+
input_tree =
|
402
|
+
[:program,
|
403
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
404
|
+
[:module,
|
405
|
+
[:const_ref, [:@const, "Foo", [1, 14]]],
|
406
|
+
[:bodystmt,
|
407
|
+
[[:void_stmt], [:var_ref, [:@ident, "x", [1, 19]]]],
|
408
|
+
nil,
|
409
|
+
nil,
|
410
|
+
nil]],
|
411
|
+
[:var_ref, [:@ident, "x", [1, 27]]]]]
|
412
|
+
output_tree =
|
413
|
+
[:program,
|
414
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
415
|
+
[:module,
|
416
|
+
[:const_ref, [:@const, "Foo", [1, 14]]],
|
417
|
+
[:bodystmt,
|
418
|
+
[[:void_stmt], [:zcall, [:@ident, "x", [1, 19]]]],
|
419
|
+
nil,
|
420
|
+
nil,
|
421
|
+
nil]],
|
422
|
+
[:var_ref, [:@ident, "x", [1, 27]]]]]
|
423
|
+
input_tree.should transform_to output_tree
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'creates closed scopes when singleton classes are opened' do
|
427
|
+
input_tree =
|
428
|
+
[:program,
|
429
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
430
|
+
[:sclass,
|
431
|
+
[:var_ref, [:@const, "String", [1, 16]]],
|
432
|
+
[:bodystmt, [[:var_ref, [:@ident, "x", [1, 24]]]], nil, nil, nil]]]]
|
433
|
+
output_tree =
|
434
|
+
[:program,
|
435
|
+
[[:assign, [:var_field, [:@ident, "x", [1, 0]]], [:@int, "5", [1, 4]]],
|
436
|
+
[:sclass,
|
437
|
+
[:var_ref, [:@const, "String", [1, 16]]],
|
438
|
+
[:bodystmt, [[:zcall, [:@ident, "x", [1, 24]]]], nil, nil, nil]]]]
|
439
|
+
input_tree.should transform_to output_tree
|
440
|
+
end
|
441
|
+
|
442
|
+
it 'refers to the enclosing environment to determine the singleton to open' do
|
443
|
+
input_tree =
|
444
|
+
[:program,
|
445
|
+
[[:assign,
|
446
|
+
[:var_field, [:@ident, "x", [1, 0]]],
|
447
|
+
[:string_literal, [:string_content, [:@tstring_content, "hi", [1, 5]]]]],
|
448
|
+
[:sclass,
|
449
|
+
[:var_ref, [:@ident, "x", [1, 19]]],
|
450
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
451
|
+
input_tree.should transform_to input_tree
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'refers to the enclosing environment to determine the singleton to open - resulting in zcall' do
|
455
|
+
input_tree =
|
456
|
+
[:program,
|
457
|
+
[[:sclass,
|
458
|
+
[:var_ref, [:@ident, "x", [1, 19]]],
|
459
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
460
|
+
output_tree =
|
461
|
+
[:program,
|
462
|
+
[[:sclass,
|
463
|
+
[:zcall, [:@ident, "x", [1, 19]]],
|
464
|
+
[:bodystmt, [[:void_stmt]], nil, nil, nil]]]]
|
465
|
+
input_tree.should transform_to output_tree
|
466
|
+
end
|
467
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ripper-plus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Edgar
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-27 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.3.0
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: yard
|
28
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: bundler
|
39
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 1.0.0
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: jeweler
|
50
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.5.2
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *id004
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rcov
|
61
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *id005
|
70
|
+
description: |-
|
71
|
+
Ripper is the Ruby parser library packaged
|
72
|
+
with Ruby 1.9. While quite complete, it has some quirks that can
|
73
|
+
make use frustrating. This gem intends to correct them.
|
74
|
+
email: michael.j.edgar@dartmouth.edu
|
75
|
+
executables: []
|
76
|
+
|
77
|
+
extensions: []
|
78
|
+
|
79
|
+
extra_rdoc_files:
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
files:
|
83
|
+
- .document
|
84
|
+
- .rspec
|
85
|
+
- Gemfile
|
86
|
+
- LICENSE.txt
|
87
|
+
- README.md
|
88
|
+
- Rakefile
|
89
|
+
- VERSION
|
90
|
+
- lib/ripper-plus.rb
|
91
|
+
- lib/ripper-plus/ripper-plus.rb
|
92
|
+
- lib/ripper-plus/scope_stack.rb
|
93
|
+
- lib/ripper-plus/transformer.rb
|
94
|
+
- spec/ripper-plus_spec.rb
|
95
|
+
- spec/scope_stack_spec.rb
|
96
|
+
- spec/spec_helper.rb
|
97
|
+
- spec/transformer_spec.rb
|
98
|
+
homepage: http://github.com/michaeledgar/ripper-plus
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
hash: 505813799173151565
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: "0"
|
121
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.7.2
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Parses Ruby code into an improved Ripper AST format
|
128
|
+
test_files:
|
129
|
+
- spec/ripper-plus_spec.rb
|
130
|
+
- spec/scope_stack_spec.rb
|
131
|
+
- spec/spec_helper.rb
|
132
|
+
- spec/transformer_spec.rb
|