zombie-killer 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +28 -0
- data/bin/zk +45 -0
- data/lib/zombie_killer.rb +4 -0
- data/lib/zombie_killer/code_histogram.rb +50 -0
- data/lib/zombie_killer/killer.rb +35 -0
- data/lib/zombie_killer/niceness.rb +60 -0
- data/lib/zombie_killer/node_type_counter.rb +29 -0
- data/lib/zombie_killer/rewriter.rb +303 -0
- data/lib/zombie_killer/variable_scope.rb +62 -0
- data/lib/zombie_killer/version.rb +3 -0
- data/spec/rspec_renderer.rb +172 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/zombie_killer_spec.md +1101 -0
- data/spec/zombie_killer_spec.rb +1148 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 45c9c420e1362bc5ed2354d40b1a7c5951530f50
|
4
|
+
data.tar.gz: 8a2d7b00e756ebc906b73120c139bda02d734912
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 157976c4b7e1cc94cc8c0384b91d69bd5909eefc1ffb15569497e9fef8fff8046a5c326b9a211e703cc4192601d1ea79a977c9c3618a33370bb6ddeba1bf2922
|
7
|
+
data.tar.gz: a8e31e77e9bab7316b6fac67ce1d36eafbdfb301aa60ae56f5239f3f8005e8e44bc501a3681235a62ddb449225b7609c24c867e8227cd9d1de1b617ba0f3f672
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 SUSE LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Zombie Killer
|
2
|
+
=============
|
3
|
+
|
4
|
+
In [YaST][y] we have tons of Ruby code which is ugly, because it was
|
5
|
+
[translated from a legacy language][yk], striving for bug-compatibility.
|
6
|
+
|
7
|
+
Zombie Killer analyzes the code for situations where it is safe
|
8
|
+
to replace the ugly variants with nice ones.
|
9
|
+
|
10
|
+
See the [runnable specification][spec] for details.
|
11
|
+
|
12
|
+
[y]: https://github.com/yast
|
13
|
+
[yk]: http://mvidner.blogspot.cz/2013/08/yast-in-ruby.html
|
14
|
+
[spec]: spec/zombie_killer_spec.md
|
15
|
+
|
16
|
+
Installation
|
17
|
+
------------
|
18
|
+
|
19
|
+
Source: clone the git repository.
|
20
|
+
|
21
|
+
Dependencies: run `bundle`.
|
22
|
+
(On openSUSE, most dependencies are packaged as rubygem-*.rpm except `unparser`)
|
23
|
+
|
24
|
+
Usage
|
25
|
+
-----
|
26
|
+
|
27
|
+
`zk [FILES...]` works in place, so it is best to use in a Git checkout.
|
28
|
+
By default it finds all `*.rb` files under the current directory.
|
data/bin/zk
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "docopt"
|
4
|
+
|
5
|
+
require_relative "../lib/zombie_killer"
|
6
|
+
|
7
|
+
doc = <<-EOT
|
8
|
+
Zombie Killer -- tool to kill YCP zombies
|
9
|
+
|
10
|
+
Usage: zk [options] [FILES...]
|
11
|
+
|
12
|
+
Arguments:
|
13
|
+
FILES Files to operate on, patterns allowed [default: **/*.rb]
|
14
|
+
Options:
|
15
|
+
-u, --unsafe Translate even constructs not known to be safe.
|
16
|
+
-s, --stats Also print statistics about node types.
|
17
|
+
-v, --version Print version information and exit.
|
18
|
+
-h, --help Print help and exit.
|
19
|
+
EOT
|
20
|
+
|
21
|
+
begin
|
22
|
+
options = Docopt.docopt(doc, help: true, version: ZombieKiller::VERSION)
|
23
|
+
|
24
|
+
killer = ZombieKiller.new
|
25
|
+
|
26
|
+
files = options["FILES"]
|
27
|
+
files << "**/*.rb" if files.empty?
|
28
|
+
files = files.flat_map do |pattern|
|
29
|
+
if pattern.include? "*"
|
30
|
+
Dir[pattern]
|
31
|
+
else
|
32
|
+
pattern
|
33
|
+
end
|
34
|
+
end
|
35
|
+
files.each do |file|
|
36
|
+
killer.kill_file(file, file, unsafe: options["--unsafe"])
|
37
|
+
|
38
|
+
if options["--stats"]
|
39
|
+
counter = NodeTypeCounter.new(file)
|
40
|
+
counter.print($stderr)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue Docopt::Exit => e
|
44
|
+
abort e.message
|
45
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class CodeHistogram
|
2
|
+
attr_reader :counts
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@counts = Hash.new do |hash, key|
|
6
|
+
hash[key] = 0
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def increment(key, value = 1)
|
11
|
+
@counts[key] += value
|
12
|
+
end
|
13
|
+
|
14
|
+
def print_by_frequency(io)
|
15
|
+
count_to_methods = invert_hash_preserving_duplicates(@counts)
|
16
|
+
|
17
|
+
count_to_methods.keys.sort.each do |c|
|
18
|
+
count_to_methods[c].sort.each do |method|
|
19
|
+
io.printf("%4d %s\n", c, method)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse_by_frequency(lines)
|
25
|
+
histogram = CodeHistogram.new
|
26
|
+
lines.each do |line|
|
27
|
+
/^\s*(\d*)\s*(.*)/.match(line.chomp) do |m|
|
28
|
+
histogram.increment(m[2], m[1].to_i)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
histogram
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge!(other)
|
35
|
+
counts.merge!(other.counts) do |key, count, other_count|
|
36
|
+
count + other_count
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def invert_hash_preserving_duplicates(h)
|
43
|
+
ih = {}
|
44
|
+
h.each do |k, v|
|
45
|
+
ih[v] = [] unless ih.has_key?(v)
|
46
|
+
ih[v] << k
|
47
|
+
end
|
48
|
+
ih
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "parser"
|
2
|
+
require "parser/current"
|
3
|
+
|
4
|
+
require_relative "rewriter"
|
5
|
+
|
6
|
+
class ZombieKiller
|
7
|
+
# @returns new string
|
8
|
+
def kill_string(code, filename = "(inline code)", unsafe: false)
|
9
|
+
fixed_point(code) do |c|
|
10
|
+
parser = Parser::CurrentRuby.new
|
11
|
+
rewriter = ZombieKillerRewriter.new(unsafe: unsafe)
|
12
|
+
buffer = Parser::Source::Buffer.new(filename)
|
13
|
+
buffer.source = c
|
14
|
+
rewriter.rewrite(buffer, parser.parse(buffer))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias_method :kill, :kill_string
|
18
|
+
|
19
|
+
# @param new_filename may be the same as *filename*
|
20
|
+
def kill_file(filename, new_filename, unsafe: false)
|
21
|
+
new_string = kill_string(File.read(filename), filename, unsafe: unsafe)
|
22
|
+
|
23
|
+
File.write(new_filename, new_string)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def fixed_point(x, &lambda_x)
|
29
|
+
while true
|
30
|
+
y = lambda_x.call(x)
|
31
|
+
return y if y == x
|
32
|
+
x = y
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# Niceness of a node means that it cannot be nil.
|
4
|
+
#
|
5
|
+
# Note that the module depends on the includer
|
6
|
+
# to provide #scope (for #nice_variable)
|
7
|
+
module Niceness
|
8
|
+
# Literals are nice, except the nil literal.
|
9
|
+
NICE_LITERAL_NODE_TYPES = [
|
10
|
+
:self,
|
11
|
+
:false, :true,
|
12
|
+
:int, :float,
|
13
|
+
:str, :sym, :regexp,
|
14
|
+
:array, :hash, :pair, :irange, # may contain nils but they are not nil
|
15
|
+
:dstr, # "String #{interpolation}" mixes :str, :begin
|
16
|
+
:dsym # :"#{foo}"
|
17
|
+
].to_set
|
18
|
+
|
19
|
+
def nice(node)
|
20
|
+
nice_literal(node) || nice_variable(node) || nice_send(node) ||
|
21
|
+
nice_begin(node)
|
22
|
+
end
|
23
|
+
|
24
|
+
def nice_literal(node)
|
25
|
+
NICE_LITERAL_NODE_TYPES.include? node.type
|
26
|
+
end
|
27
|
+
|
28
|
+
def nice_variable(node)
|
29
|
+
node.type == :lvar && scope[node.children.first].nice
|
30
|
+
end
|
31
|
+
|
32
|
+
# Methods that preserve niceness if all their arguments are nice
|
33
|
+
# These are global, called with a nil receiver
|
34
|
+
NICE_GLOBAL_METHODS = {
|
35
|
+
# message, number of arguments
|
36
|
+
:_ => 1,
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
NICE_OPERATORS = {
|
40
|
+
# message, number of arguments (other than receiver)
|
41
|
+
:+ => 1,
|
42
|
+
}.freeze
|
43
|
+
|
44
|
+
def nice_send(node)
|
45
|
+
return false unless node.type == :send
|
46
|
+
receiver, message, *args = *node
|
47
|
+
|
48
|
+
if receiver.nil?
|
49
|
+
arity = NICE_GLOBAL_METHODS.fetch(message, -1)
|
50
|
+
else
|
51
|
+
return false unless nice(receiver)
|
52
|
+
arity = NICE_OPERATORS.fetch(message, -1)
|
53
|
+
end
|
54
|
+
return args.size == arity && args.all?{ |a| nice(a) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def nice_begin(node)
|
58
|
+
node.type == :begin && nice(node.children.last)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "parser"
|
2
|
+
|
3
|
+
require_relative "code_histogram"
|
4
|
+
|
5
|
+
class NodeTypeCounter < Parser::Rewriter
|
6
|
+
attr_reader :node_types
|
7
|
+
|
8
|
+
def initialize(filename)
|
9
|
+
@node_types = CodeHistogram.new
|
10
|
+
@filename = filename
|
11
|
+
end
|
12
|
+
|
13
|
+
def process(node)
|
14
|
+
return if node.nil?
|
15
|
+
@node_types.increment(node.type)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
def print(io)
|
20
|
+
parser = Parser::CurrentRuby.new
|
21
|
+
buffer = Parser::Source::Buffer.new(@filename)
|
22
|
+
buffer.read
|
23
|
+
ast = parser.parse(buffer)
|
24
|
+
|
25
|
+
process(ast)
|
26
|
+
|
27
|
+
@node_types.print_by_frequency(io)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require "parser"
|
2
|
+
require "parser/current"
|
3
|
+
require "set"
|
4
|
+
require "unparser"
|
5
|
+
|
6
|
+
require_relative "niceness"
|
7
|
+
require_relative "variable_scope"
|
8
|
+
|
9
|
+
# We have encountered code that does satisfy our simplifying assumptions,
|
10
|
+
# translating it would not be correct.
|
11
|
+
class TooComplexToTranslateError < Exception
|
12
|
+
end
|
13
|
+
|
14
|
+
class ZombieKillerRewriter < Parser::Rewriter
|
15
|
+
include Niceness
|
16
|
+
|
17
|
+
attr_reader :scopes
|
18
|
+
|
19
|
+
def initialize(unsafe: false)
|
20
|
+
@scopes = VariableScopeStack.new
|
21
|
+
@unsafe = unsafe
|
22
|
+
end
|
23
|
+
|
24
|
+
def warning(message)
|
25
|
+
$stderr.puts message if $VERBOSE
|
26
|
+
end
|
27
|
+
|
28
|
+
def rewrite(buffer, ast)
|
29
|
+
super
|
30
|
+
rescue TooComplexToTranslateError
|
31
|
+
warning "(outer scope) is too complex to translate"
|
32
|
+
buffer.source
|
33
|
+
end
|
34
|
+
|
35
|
+
# FIXME
|
36
|
+
# How can we ensure that code modifications do not make some unhandled again?
|
37
|
+
HANDLED_NODE_TYPES = [
|
38
|
+
:alias, # Method alias
|
39
|
+
:and, # &&
|
40
|
+
:and_asgn, # &&=
|
41
|
+
:arg, # One argument
|
42
|
+
:args, # All arguments
|
43
|
+
:back_ref, # Regexp backreference, $`; $&; $'
|
44
|
+
:begin, # A simple sequence
|
45
|
+
:block, # A closure, not just any scope
|
46
|
+
:block_pass, # Pass &foo as an arg which is a block, &:foo
|
47
|
+
:blockarg, # An argument initialized with a block def m(&b)
|
48
|
+
:break, # Break statement
|
49
|
+
:case, # Case statement
|
50
|
+
:casgn, # Constant assignment/definition
|
51
|
+
:cbase, # Base/root of constant tree, ::Foo
|
52
|
+
:class, # Class body
|
53
|
+
:cvar, # Class @@variable
|
54
|
+
:cvasgn, # Class @@variable = assignment
|
55
|
+
:const, # Name of a class/module or name of a value
|
56
|
+
:def, # Method definition
|
57
|
+
:defined?, # defined? statement
|
58
|
+
:defs, # Method definition on self
|
59
|
+
:ensure, # Exception ensuring
|
60
|
+
:for, # For v in enum;
|
61
|
+
:gvar, # Global $variable
|
62
|
+
:gvasgn, # Global $variable = assignment
|
63
|
+
:if, # If and Unless
|
64
|
+
:ivar, # Instance variable value
|
65
|
+
:ivasgn, # Instance variable assignment
|
66
|
+
:kwbegin, # A variant of begin; for rescue and while_post
|
67
|
+
:kwoptarg, # Keyword optional argument, def m(a: 1)
|
68
|
+
:lvar, # Local variable value
|
69
|
+
:lvasgn, # Local variable assignment
|
70
|
+
:masgn, # Multiple assigment: a, b = c, d
|
71
|
+
:mlhs, # Left-hand side of a multiple assigment: a, b = c, d
|
72
|
+
:module, # Module body
|
73
|
+
:next, # Next statement
|
74
|
+
:nil, # nil literal
|
75
|
+
:nth_ref, # Regexp back references: $1, $2...
|
76
|
+
:op_asgn, # a %= b where % is any operator except || &&
|
77
|
+
:optarg, # Optional argument
|
78
|
+
:or, # ||
|
79
|
+
:or_asgn, # ||=
|
80
|
+
:postexe, # END { }
|
81
|
+
:regopt, # options tacked on a :regexp
|
82
|
+
:resbody, # One rescue clause in a :rescue construct
|
83
|
+
:rescue, # Groups the begin and :resbody
|
84
|
+
:restarg, # Rest of arguments, (..., *args)
|
85
|
+
:retry, # Retry a begin-rescue block
|
86
|
+
:return, # Method return
|
87
|
+
:sclass, # Singleton class, class << foo
|
88
|
+
:send, # Send a message AKA Call a method
|
89
|
+
:splat, # Array *splatting
|
90
|
+
:super, # Call the ancestor method
|
91
|
+
:unless, # Unless AKA If-Not
|
92
|
+
:until, # Until AKA While-Not
|
93
|
+
:until_post, # Until with post-condtion
|
94
|
+
:when, # When branch of an Case statement
|
95
|
+
:while, # While loop
|
96
|
+
:while_post, # While loop with post-condition
|
97
|
+
:xstr, # Executed `string`, backticks
|
98
|
+
:yield, # Call the unnamed block
|
99
|
+
:zsuper # Zero argument :super
|
100
|
+
].to_set + NICE_LITERAL_NODE_TYPES
|
101
|
+
|
102
|
+
def process(node)
|
103
|
+
return if node.nil?
|
104
|
+
if ! @unsafe
|
105
|
+
oops(node, RuntimeError.new("Unknown node type #{node.type}")) unless
|
106
|
+
HANDLED_NODE_TYPES.include? node.type
|
107
|
+
end
|
108
|
+
super
|
109
|
+
end
|
110
|
+
|
111
|
+
# currently visible scope
|
112
|
+
def scope
|
113
|
+
scopes.innermost
|
114
|
+
end
|
115
|
+
|
116
|
+
def with_new_scope_rescuing_oops(&block)
|
117
|
+
scopes.with_new do
|
118
|
+
block.call
|
119
|
+
end
|
120
|
+
rescue => e
|
121
|
+
oops(node, e)
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_def(node)
|
125
|
+
with_new_scope_rescuing_oops { super }
|
126
|
+
end
|
127
|
+
|
128
|
+
def on_defs(node)
|
129
|
+
with_new_scope_rescuing_oops { super }
|
130
|
+
end
|
131
|
+
|
132
|
+
def on_module(node)
|
133
|
+
with_new_scope_rescuing_oops { super }
|
134
|
+
end
|
135
|
+
|
136
|
+
def on_class(node)
|
137
|
+
with_new_scope_rescuing_oops { super }
|
138
|
+
end
|
139
|
+
|
140
|
+
def on_sclass(node)
|
141
|
+
with_new_scope_rescuing_oops { super }
|
142
|
+
end
|
143
|
+
|
144
|
+
def on_if(node)
|
145
|
+
cond, then_body, else_body = *node
|
146
|
+
process(cond)
|
147
|
+
|
148
|
+
scopes.with_copy do
|
149
|
+
process(then_body)
|
150
|
+
end
|
151
|
+
|
152
|
+
scopes.with_copy do
|
153
|
+
process(else_body)
|
154
|
+
end
|
155
|
+
|
156
|
+
# clean slate
|
157
|
+
scope.clear
|
158
|
+
end
|
159
|
+
|
160
|
+
# def on_unless
|
161
|
+
# Does not exist.
|
162
|
+
# `unless` is parsed as an `if` with then_body and else_body swapped.
|
163
|
+
# Compare with `while` and `until` which cannot do that and thus need
|
164
|
+
# distinct node types.
|
165
|
+
# end
|
166
|
+
|
167
|
+
def on_case(node)
|
168
|
+
expr, *cases = *node
|
169
|
+
process(expr)
|
170
|
+
|
171
|
+
cases.each do |case_|
|
172
|
+
scopes.with_copy do
|
173
|
+
process(case_)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# clean slate
|
178
|
+
scope.clear
|
179
|
+
end
|
180
|
+
|
181
|
+
def on_lvasgn(node)
|
182
|
+
super
|
183
|
+
name, value = * node
|
184
|
+
return if value.nil? # and-asgn, or-asgn, resbody do this
|
185
|
+
scope[name].nice = nice(value)
|
186
|
+
end
|
187
|
+
|
188
|
+
def on_and_asgn(node)
|
189
|
+
super
|
190
|
+
var, value = * node
|
191
|
+
return if var.type != :lvasgn
|
192
|
+
name = var.children[0]
|
193
|
+
|
194
|
+
scope[name].nice &&= nice(value)
|
195
|
+
end
|
196
|
+
|
197
|
+
def on_or_asgn(node)
|
198
|
+
super
|
199
|
+
var, value = * node
|
200
|
+
return if var.type != :lvasgn
|
201
|
+
name = var.children[0]
|
202
|
+
|
203
|
+
scope[name].nice ||= nice(value)
|
204
|
+
end
|
205
|
+
|
206
|
+
def on_send(node)
|
207
|
+
super
|
208
|
+
if is_call(node, :Ops, :add)
|
209
|
+
new_op = :+
|
210
|
+
|
211
|
+
_ops, _add, a, b = *node
|
212
|
+
if nice(a) && nice(b)
|
213
|
+
replace_node node, Parser::AST::Node.new(:send, [a, new_op, b])
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def on_block(node)
|
219
|
+
# ignore body, clean slate
|
220
|
+
scope.clear
|
221
|
+
end
|
222
|
+
alias_method :on_for, :on_block
|
223
|
+
|
224
|
+
def on_while(node)
|
225
|
+
# ignore both condition and body,
|
226
|
+
# with a simplistic scope we cannot handle them
|
227
|
+
|
228
|
+
# clean slate
|
229
|
+
scope.clear
|
230
|
+
end
|
231
|
+
alias_method :on_until, :on_while
|
232
|
+
|
233
|
+
# Exceptions:
|
234
|
+
# `raise` is an ordinary :send for the parser
|
235
|
+
|
236
|
+
def on_rescue(node)
|
237
|
+
# (:rescue, begin-block, resbody..., else-block-or-nil)
|
238
|
+
begin_body, *rescue_bodies, else_body = *node
|
239
|
+
|
240
|
+
@source_rewriter.transaction do
|
241
|
+
process(begin_body)
|
242
|
+
process(else_body)
|
243
|
+
rescue_bodies.each do |r|
|
244
|
+
process(r)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
rescue TooComplexToTranslateError
|
248
|
+
warning "begin-rescue is too complex to translate due to a retry"
|
249
|
+
end
|
250
|
+
|
251
|
+
def on_resbody(node)
|
252
|
+
# How it is parsed:
|
253
|
+
# (:resbody, exception-types-or-nil, exception-variable-or-nil, body)
|
254
|
+
# exception-types is an :array
|
255
|
+
# exception-variable is a (:lvasgn, name), without a value
|
256
|
+
|
257
|
+
# A rescue means that *some* previous code was skipped. We know nothing.
|
258
|
+
# We could process the resbodies individually,
|
259
|
+
# and join begin-block with else-block, but it is little worth
|
260
|
+
# because they will contain few zombies.
|
261
|
+
scope.clear
|
262
|
+
super
|
263
|
+
end
|
264
|
+
|
265
|
+
def on_ensure(node)
|
266
|
+
# (:ensure, guarded-code, ensuring-code)
|
267
|
+
# guarded-code may be a :rescue or not
|
268
|
+
|
269
|
+
scope.clear
|
270
|
+
end
|
271
|
+
|
272
|
+
def on_retry(node)
|
273
|
+
# that makes the :rescue a loop, top-down data-flow fails
|
274
|
+
raise TooComplexToTranslateError
|
275
|
+
end
|
276
|
+
|
277
|
+
private
|
278
|
+
|
279
|
+
def oops(node, exception)
|
280
|
+
puts "Node exception @ #{node.loc.expression}"
|
281
|
+
puts "Offending node: #{node.inspect}"
|
282
|
+
raise exception
|
283
|
+
end
|
284
|
+
|
285
|
+
def is_call(node, namespace, message)
|
286
|
+
n_receiver, n_message = *node
|
287
|
+
n_receiver && n_receiver.type == :const &&
|
288
|
+
n_receiver.children[0] == nil &&
|
289
|
+
n_receiver.children[1] == namespace &&
|
290
|
+
n_message == message
|
291
|
+
end
|
292
|
+
|
293
|
+
def replace_node(old_node, new_node)
|
294
|
+
source_range = old_node.loc.expression
|
295
|
+
if !contains_comment?(source_range.source)
|
296
|
+
replace(source_range, Unparser.unparse(new_node))
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def contains_comment?(string)
|
301
|
+
/^[^'"\n]*#/.match(string)
|
302
|
+
end
|
303
|
+
end
|