class Synvert::Core::Rewriter::Instance

Instance is an execution unit, it finds specified ast nodes, checks if the nodes match some conditions, then add, replace or remove code.

One instance can contains one or many [Synvert::Core::Rewriter::Scope] and [Synvert::Rewriter::Condition].

Attributes

current_file[RW]

@!attribute [rw] current_node

@return current parsing node

@!attribute [rw] current_file

@return current filename
current_node[RW]

@!attribute [rw] current_node

@return current parsing node

@!attribute [rw] current_file

@return current filename

Public Class Methods

file_ast(file_path) click to toggle source

Cached file ast.

@param file_path [String] file path @return [String] ast node for file

# File lib/synvert/core/rewriter/instance.rb, line 30
def file_ast(file_path)
  @file_ast ||= {}
  @file_ast[file_path] ||=
    begin
      buffer = Parser::Source::Buffer.new file_path
      buffer.source = file_source(file_path)

      parser = Parser::CurrentRuby.new
      parser.reset
      parser.parse buffer
    end
end
file_source(file_path) click to toggle source

Cached file source.

@param file_path [String] file path @return [String] file source

# File lib/synvert/core/rewriter/instance.rb, line 16
def file_source(file_path)
  @file_source ||= {}
  @file_source[file_path] ||=
    begin
      source = File.read(file_path, encoding: 'UTF-8')
      source = Engine::ERB.encode(source) if /\.erb$/.match?(file_path)
      source
    end
end
new(rewriter, file_pattern, &block) click to toggle source

Initialize an instance.

@param rewriter [Synvert::Core::Rewriter] @param file_pattern [String] pattern to find files, e.g. spec/*/_spec.rb @param block [Block] block code to find nodes, match conditions and rewrite code. @return [Synvert::Core::Rewriter::Instance]

# File lib/synvert/core/rewriter/instance.rb, line 78
def initialize(rewriter, file_pattern, &block)
  @rewriter = rewriter
  @actions = []
  @file_pattern = file_pattern
  @block = block
  rewriter.helpers.each { |helper| singleton_class.send(:define_method, helper[:name], &helper[:block]) }
end
reset() click to toggle source

Reset cached file source and ast.

# File lib/synvert/core/rewriter/instance.rb, line 55
def reset
  @file_source = {}
  @file_ast = {}
end
write_file(file_path, source) click to toggle source

Write source to file and remove cached file source and ast.

@param file_path [String] file path @param source [String] file source

# File lib/synvert/core/rewriter/instance.rb, line 47
def write_file(file_path, source)
  source = Engine::ERB.decode(source) if /\.erb/.match?(file_path)
  File.write(file_path, source.gsub(/ +\n/, "\n"))
  @file_source[file_path] = nil
  @file_ast[file_path] = nil
end

Public Instance Methods

any_value() click to toggle source

Any value but nil.

# File lib/synvert/core/rewriter/instance.rb, line 304
def any_value
  Rewriter::AnyValue.new
end
append(code) click to toggle source

Parse append dsl, it creates a [Synvert::Core::Rewriter::AppendAction] to append the code to the bottom of current node body.

@param code [String] code need to be appended.

# File lib/synvert/core/rewriter/instance.rb, line 223
def append(code)
  @actions << Rewriter::AppendAction.new(self, code)
end
delete(*selectors) click to toggle source

Parse delete dsl, it creates a [Synvert::Core::Rewriter::DeleteAction] to delete child nodes.

@param selectors [Array<Symbol>] selector names of child node.

# File lib/synvert/core/rewriter/instance.rb, line 283
def delete(*selectors)
  @actions << Rewriter::DeleteAction.new(self, *selectors)
end
file_source() click to toggle source

Current file source

# File lib/synvert/core/rewriter/instance.rb, line 68
def file_source
  self.class.file_source(current_file)
end
goto_node(child_node_name, &block) click to toggle source

Parse goto_node dsl, it creates a [Synvert::Core::Rewriter::GotoScope] to go to a child node, then continue operating on the child node.

@param child_node_name [Symbol|String] the name of the child nodes. @param block [Block] block code to continue operating on the matching nodes.

# File lib/synvert/core/rewriter/instance.rb, line 187
def goto_node(child_node_name, &block)
  Rewriter::GotoScope.new(self, child_node_name, &block).process
end
if_exist_node(rules, &block) click to toggle source

Parse if_exist_node dsl, it creates a [Synvert::Core::Rewriter::IfExistCondition] to check if matching nodes exist in the child nodes, if so, then continue operating on each matching ast node.

@param rules [Hash] rules to check mathing ast nodes. @param block [Block] block code to continue operating on the matching nodes.

# File lib/synvert/core/rewriter/instance.rb, line 196
def if_exist_node(rules, &block)
  Rewriter::IfExistCondition.new(self, rules, &block).process
end
if_only_exist_node(rules, &block) click to toggle source

Parse if_only_exist_node dsl, it creates a [Synvert::Core::Rewriter::IfOnlyExistCondition] to check if current node has only one child node and the child node matches rules, if so, then continue operating on each matching ast node.

@param rules [Hash] rules to check mathing ast nodes. @param block [Block] block code to continue operating on the matching nodes.

# File lib/synvert/core/rewriter/instance.rb, line 215
def if_only_exist_node(rules, &block)
  Rewriter::IfOnlyExistCondition.new(self, rules, &block).process
end
insert(code, at: 'end') click to toggle source

Parse insert dsl, it creates a [Synvert::Core::Rewriter::InsertAction] to insert the code to the top of current node body.

@param code [String] code need to be inserted. @param at [String] insert position, beginning or end, end is the default.

# File lib/synvert/core/rewriter/instance.rb, line 240
def insert(code, at: 'end')
  @actions << Rewriter::InsertAction.new(self, code, at: at)
end
insert_after(node) click to toggle source

Parse insert_after dsl, it creates a [Synvert::Core::Rewriter::InsertAfterAction] to insert the code next to the current node.

@param code [String] code need to be inserted.

# File lib/synvert/core/rewriter/instance.rb, line 248
def insert_after(node)
  @actions << Rewriter::InsertAfterAction.new(self, node)
end
node() click to toggle source

Gets current node, it allows to get current node in block code.

@return [Parser::AST::Node]

# File lib/synvert/core/rewriter/instance.rb, line 131
def node
  @current_node
end
prepend(code) click to toggle source

Parse prepend dsl, it creates a [Synvert::Core::Rewriter::PrependAction] to prepend the code to the top of current node body.

@param code [String] code need to be prepended.

# File lib/synvert/core/rewriter/instance.rb, line 231
def prepend(code)
  @actions << Rewriter::PrependAction.new(self, code)
end
process() click to toggle source

Process the instance. It finds all files, for each file, it executes the block code, gets all rewrite actions, and rewrite source code back to original file.

# File lib/synvert/core/rewriter/instance.rb, line 89
def process
  file_pattern = File.join(Configuration.path, @file_pattern)
  Dir.glob(file_pattern).each do |file_path|
    next if Configuration.skip_files.include? file_path

    begin
      puts file_path if Configuration.show_run_process
      conflict_actions = []
      source = +self.class.file_source(file_path)
      ast = self.class.file_ast(file_path)

      @current_file = file_path

      process_with_node ast do
        begin
          instance_eval(&@block)
        rescue NoMethodError
          puts @current_node.debug_info
          raise
        end
      end

      if @actions.length > 0
        @actions.sort_by! { |action| [action.begin_pos, action.end_pos] }
        conflict_actions = get_conflict_actions
        @actions.reverse_each do |action|
          source[action.begin_pos...action.end_pos] = action.rewritten_code
        end
        @actions = []

        update_file(file_path, source)
      end
    rescue Parser::SyntaxError
      puts "[Warn] file #{file_path} was not parsed correctly."
      # do nothing, iterate next file
    end while !conflict_actions.empty?
  end
end
process_with_node(node) { || ... } click to toggle source

Set current_node to node and process.

@param node [Parser::AST::Node] node set to current_node @yield process

# File lib/synvert/core/rewriter/instance.rb, line 139
def process_with_node(node)
  self.current_node = node
  yield
  self.current_node = node
end
process_with_other_node(node) { || ... } click to toggle source

Set current_node properly, process and set current_node back to original current_node.

@param node [Parser::AST::Node] node set to other_node @yield process

# File lib/synvert/core/rewriter/instance.rb, line 149
def process_with_other_node(node)
  original_node = current_node
  self.current_node = node
  yield
  self.current_node = original_node
end
remove() click to toggle source

Parse remove dsl, it creates a [Synvert::Core::Rewriter::RemoveAction] to remove current node.

# File lib/synvert/core/rewriter/instance.rb, line 276
def remove
  @actions << Rewriter::RemoveAction.new(self)
end
replace(*selectors, with:) click to toggle source

Parse replace with dsl, it creates a [Synvert::Core::Rewriter::ReplaceAction] to replace child nodes with code.

@param selectors [Array<Symbol>] selector names of child node. @param with [String] code need to be replaced with.

# File lib/synvert/core/rewriter/instance.rb, line 265
def replace(*selectors, with:)
  @actions << Rewriter::ReplaceAction.new(self, *selectors, with: with)
end
replace_erb_stmt_with_expr() click to toggle source

Parse replace_erb_stmt_with_expr dsl, it creates a [Synvert::Core::Rewriter::ReplaceErbStmtWithExprAction] to replace erb stmt code to expr code.

# File lib/synvert/core/rewriter/instance.rb, line 271
def replace_erb_stmt_with_expr
  @actions << Rewriter::ReplaceErbStmtWithExprAction.new(self)
end
replace_with(code) click to toggle source

Parse replace_with dsl, it creates a [Synvert::Core::Rewriter::ReplaceWithAction] to replace current node with code.

@param code [String] code need to be replaced with.

# File lib/synvert/core/rewriter/instance.rb, line 256
def replace_with(code)
  @actions << Rewriter::ReplaceWithAction.new(self, code)
end
unless_exist_node(rules, &block) click to toggle source

Parse unless_exist_node dsl, it creates a [Synvert::Core::Rewriter::UnlessExistCondition] to check if matching nodes doesn't exist in the child nodes, if so, then continue operating on each matching ast node.

@param rules [Hash] rules to check mathing ast nodes. @param block [Block] block code to continue operating on the matching nodes.

# File lib/synvert/core/rewriter/instance.rb, line 205
def unless_exist_node(rules, &block)
  Rewriter::UnlessExistCondition.new(self, rules, &block).process
end
warn(message) click to toggle source

Parse warn dsl, it creates a [Synvert::Core::Rewriter::Warning] to save warning message.

@param message [String] warning message.

# File lib/synvert/core/rewriter/instance.rb, line 299
def warn(message)
  @rewriter.add_warning Rewriter::Warning.new(self, message)
end
with_direct_node(rules, &block)
Alias for: within_direct_node
with_node(rules, &block)
Alias for: within_node
within_direct_node(rules, &block) click to toggle source

Parse within_direct_node dsl, it creates a [Synvert::Core::Rewriter::WithinScope] to find direct matching ast nodes, then continue operating on each matching ast node.

@param rules [Hash] rules to find mathing ast nodes. @param block [Block] block code to continue operating on the matching nodes.

# File lib/synvert/core/rewriter/instance.rb, line 176
def within_direct_node(rules, &block)
  Rewriter::WithinScope.new(self, rules, { recursive: false }, &block).process
end
Also aliased as: with_direct_node
within_node(rules, &block) click to toggle source

Parse within_node dsl, it creates a [Synvert::Core::Rewriter::WithinScope] to find recursive matching ast nodes, then continue operating on each matching ast node.

@param rules [Hash] rules to find mathing ast nodes. @param block [Block] block code to continue operating on the matching nodes.

# File lib/synvert/core/rewriter/instance.rb, line 165
def within_node(rules, &block)
  Rewriter::WithinScope.new(self, rules, { recursive: true }, &block).process
end
Also aliased as: with_node
wrap(with:, indent: nil) click to toggle source

Parse wrap with dsl, it creates a [Synvert::Core::Rewriter::WrapAction] to wrap current node with code.

@param with [String] code need to be wrapped with. @param indent [Integer] number of whitespaces.

# File lib/synvert/core/rewriter/instance.rb, line 292
def wrap(with:, indent: nil)
  @actions << Rewriter::WrapAction.new(self, with: with, indent: indent)
end

Private Instance Methods

get_conflict_actions() click to toggle source

It changes source code from bottom to top, and it can change source code twice at the same time, So if there is an overlap between two actions, it removes the conflict actions and operate them in the next loop.

# File lib/synvert/core/rewriter/instance.rb, line 312
def get_conflict_actions
  i = @actions.length - 1
  j = i - 1
  conflict_actions = []
  return if i < 0

  begin_pos = @actions[i].begin_pos
  while j > -1
    if begin_pos < @actions[j].end_pos
      conflict_actions << @actions.delete_at(j)
    else
      i = j
      begin_pos = @actions[i].begin_pos
    end
    j -= 1
  end
  conflict_actions
end
update_file(file_path, source) click to toggle source

It updates a file with new source code.

@param file_path [String] the file path @param source [String] the new source code

# File lib/synvert/core/rewriter/instance.rb, line 335
def update_file(file_path, source)
  self.class.write_file(file_path, source)
  @rewriter.add_affected_file(file_path)
end