class ZombieKillerRewriter
The main rewriter
Constants
- HANDLED_NODE_TYPES
FIXME How can we ensure that code modifications do not make some unhandled again?
Attributes
scopes[R]
Public Class Methods
new(unsafe: false)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 32 def initialize(unsafe: false) @scopes = VariableScopeStack.new @unsafe = unsafe end
Public Instance Methods
on_and_asgn(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 207 def on_and_asgn(node) super var, value = * node return if var.type != :lvasgn name = var.children[0] scope[name].nice &&= nice(value) end
on_block(_node)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 237 def on_block(_node) # ignore body, clean slate scope.clear end
Also aliased as: on_for
on_case(node)
click to toggle source
def on_unless Does not exist. `unless` is parsed as an `if` with then_body and else_body swapped. Compare with `while` and `until` which cannot do that and thus need distinct node types. end
# File lib/zombie_killer/rewriter.rb, line 186 def on_case(node) expr, *cases = *node process(expr) cases.each do |case_| scopes.with_copy do process(case_) end end # clean slate scope.clear end
on_class(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 155 def on_class(node) with_new_scope_rescuing_oops { super } end
on_def(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 143 def on_def(node) with_new_scope_rescuing_oops { super } end
on_defs(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 147 def on_defs(node) with_new_scope_rescuing_oops { super } end
on_ensure(_node)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 284 def on_ensure(_node) # (:ensure, guarded-code, ensuring-code) # guarded-code may be a :rescue or not scope.clear end
on_if(node)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 163 def on_if(node) cond, then_body, else_body = *node process(cond) scopes.with_copy do process(then_body) end scopes.with_copy do process(else_body) end # clean slate scope.clear end
on_lvasgn(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 200 def on_lvasgn(node) super name, value = * node return if value.nil? # and-asgn, or-asgn, resbody do this scope[name].nice = nice(value) end
on_module(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 151 def on_module(node) with_new_scope_rescuing_oops { super } end
on_or_asgn(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 216 def on_or_asgn(node) super var, value = * node return if var.type != :lvasgn name = var.children[0] scope[name].nice ||= nice(value) end
on_resbody(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 270 def on_resbody(node) # How it is parsed: # (:resbody, exception-types-or-nil, exception-variable-or-nil, body) # exception-types is an :array # exception-variable is a (:lvasgn, name), without a value # A rescue means that *some* previous code was skipped. We know nothing. # We could process the resbodies individually, # and join begin-block with else-block, but it is little worth # because they will contain few zombies. scope.clear super end
on_rescue(node)
click to toggle source
Exceptions: `raise` is an ordinary :send for the parser
# File lib/zombie_killer/rewriter.rb, line 255 def on_rescue(node) # (:rescue, begin-block, resbody..., else-block-or-nil) begin_body, *rescue_bodies, else_body = *node @source_rewriter.transaction do process(begin_body) process(else_body) rescue_bodies.each do |r| process(r) end end rescue TooComplexToTranslateError warning "begin-rescue is too complex to translate due to a retry" end
on_retry(_node)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 291 def on_retry(_node) # that makes the :rescue a loop, top-down data-flow fails raise TooComplexToTranslateError end
on_sclass(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 159 def on_sclass(node) with_new_scope_rescuing_oops { super } end
on_send(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 225 def on_send(node) super if call?(node, :Ops, :add) new_op = :+ _ops, _add, a, b = *node if nice(a) && nice(b) replace_node node, Parser::AST::Node.new(:send, [a, new_op, b]) end end end
on_while(_node)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 243 def on_while(_node) # ignore both condition and body, # with a simplistic scope we cannot handle them # clean slate scope.clear end
Also aliased as: on_until
process(node)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 119 def process(node) return if node.nil? unless @unsafe raise NodeError.new("Unknown node type #{node.type}", node) unless HANDLED_NODE_TYPES.include? node.type end super end
rewrite(buffer, ast)
click to toggle source
Calls superclass method
# File lib/zombie_killer/rewriter.rb, line 41 def rewrite(buffer, ast) super rescue TooComplexToTranslateError warning "(outer scope) is too complex to translate" buffer.source end
scope()
click to toggle source
currently visible scope
# File lib/zombie_killer/rewriter.rb, line 129 def scope scopes.innermost end
warning(message)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 37 def warning(message) $stderr.puts message if $VERBOSE end
with_new_scope_rescuing_oops(&block)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 133 def with_new_scope_rescuing_oops(&block) scopes.with_new do block.call end rescue NodeError => e puts e puts "Node exception @ #{e.node.loc.expression}" puts "Offending node: #{e.node.inspect}" end
Private Instance Methods
call?(node, namespace, message)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 298 def call?(node, namespace, message) n_receiver, n_message = *node n_receiver && n_receiver.type == :const && n_receiver.children[0].nil? && n_receiver.children[1] == namespace && n_message == message end
contains_comment?(string)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 312 def contains_comment?(string) /^[^'"\n]*#/.match(string) end
replace_node(old_node, new_node)
click to toggle source
# File lib/zombie_killer/rewriter.rb, line 306 def replace_node(old_node, new_node) source_range = old_node.loc.expression return if contains_comment?(source_range.source) replace(source_range, Unparser.unparse(new_node)) end