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_for(_node)
Alias for: on_block
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_until(_node)
Alias for: on_while
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