Class: Synvert::Core::Rewriter::Instance

Inherits:
Object
  • Object
show all
Includes:
Helper
Defined in:
lib/synvert/core/rewriter/instance.rb

Overview

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 contain one or many Scope and Condition.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helper

#add_arguments_with_parenthesis_if_necessary, #add_curly_brackets_if_necessary, #add_receiver_if_necessary, #reject_keys_from_hash, #strip_brackets

Constructor Details

#initialize(rewriter, file_patterns) { ... } ⇒ Instance

Initialize an Instance.

Parameters:

  • rewriter (Synvert::Core::Rewriter)
  • file_patterns (Array<String>)

    pattern list to find files, e.g. [‘spec/*/_spec.rb’]

Yields:

  • block code to find nodes, match conditions and rewrite code.



15
16
17
18
19
20
21
# File 'lib/synvert/core/rewriter/instance.rb', line 15

def initialize(rewriter, file_patterns, &block)
  @rewriter = rewriter
  @actions = []
  @file_patterns = file_patterns
  @block = block
  rewriter.helpers.each { |helper| singleton_class.send(:define_method, helper[:name], &helper[:block]) }
end

Instance Attribute Details

#current_fileObject

Returns current filename.

Returns:

  • current filename



29
# File 'lib/synvert/core/rewriter/instance.rb', line 29

attr_accessor :current_node, :current_file, :current_mutation

#current_mutationObject

Returns current mutation.

Returns:

  • current mutation



29
# File 'lib/synvert/core/rewriter/instance.rb', line 29

attr_accessor :current_node, :current_file, :current_mutation

#current_nodeObject

Returns current parsing node.

Returns:

  • current parsing node



29
30
31
# File 'lib/synvert/core/rewriter/instance.rb', line 29

def current_node
  @current_node
end

Instance Method Details

#any_valueSynvert::Core::Rewriter::AnyValue

Match any value but nil.

Examples:

type: 'hash', nothing_value: 'true', status_value: any_value

Returns:



337
338
339
# File 'lib/synvert/core/rewriter/instance.rb', line 337

def any_value
  Rewriter::AnyValue.new
end

#append(code) ⇒ Object

Parse append dsl, it creates a AppendAction to append the code to the bottom of current node body.

Examples:

# def teardown
#   clean_something
# end
# =>
# def teardown
#   clean_something
#   super
# end
with_node type: 'def', name: 'steardown' do
  append 'super'
end

Parameters:

  • code (String)

    code need to be appended.



181
182
183
# File 'lib/synvert/core/rewriter/instance.rb', line 181

def append(code)
  @current_mutation.append(@current_node, code)
end

#delete(*selectors, **options) ⇒ Object

Parse delete dsl, it creates a DeleteAction to delete child nodes.

Examples:

# FactoryBot.create(...)
# =>
# create(...)
with_node type: 'send', receiver: 'FactoryBot', message: 'create' do
  delete :receiver, :dot
end

Parameters:

  • selectors (Array<Symbol>)

    selector names of child node.

  • options (Hash)
  • and_comma (Hash)

    a customizable set of options



301
302
303
# File 'lib/synvert/core/rewriter/instance.rb', line 301

def delete(*selectors, **options)
  @current_mutation.delete(@current_node, *selectors, **options)
end

#find_node(nql, options = {}) { ... } ⇒ Object

Parse find_node dsl, it creates QueryScope to recursively find matching ast nodes, then continue operating on each matching ast node.

Examples:

# matches FactoryBot.create(:user)
find_node '.send[receiver=FactoryBot][message=create][arguments.size=1]' do
end

Parameters:

  • nql (String)

    node query language to find matching ast nodes.

Yields:

  • run on the matching nodes.

Raises:

  • (Synvert::Core::NodeQuery::Compiler::ParseError)

    if query string is invalid.



85
86
87
88
89
# File 'lib/synvert/core/rewriter/instance.rb', line 85

def find_node(nql, options = {}, &block)
  Rewriter::QueryScope.new(self, nql, options, &block).process
rescue NodeQueryLexer::ScanError, Racc::ParseError => e
  raise NodeQuery::Compiler::ParseError, "Invalid query string: #{nql}"
end

#goto_node(child_node_name, &block) ⇒ Object

Parse goto_node dsl, it creates a GotoScope to go to a child node, then continue operating on the child node.

Examples:

# head status: 406
with_node type: 'send', receiver: nil, message: 'head', arguments: { size: 1, first: { type: 'hash' } } do
  goto_node 'arguments.first' do
  end
end

Parameters:

  • child_node_name (Symbol|String)

    the name of the child nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



119
120
121
# File 'lib/synvert/core/rewriter/instance.rb', line 119

def goto_node(child_node_name, &block)
  Rewriter::GotoScope.new(self, child_node_name, &block).process
end

#if_exist_node(rules, &block) ⇒ Object

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.

Examples:

# Klass.any_instance.stub(:message)
with_node type: 'send', message: 'stub', arguments: { first: { type: { not: 'hash' } } } do
  if_exist_node type: 'send', message: 'any_instance' do
  end
end

Parameters:

  • rules (Hash)

    rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



133
134
135
# File 'lib/synvert/core/rewriter/instance.rb', line 133

def if_exist_node(rules, &block)
  Rewriter::IfExistCondition.new(self, rules, &block).process
end

#if_only_exist_node(rules, &block) ⇒ Object

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.

Examples:

# it { should matcher }
with_node type: 'block', caller: { message: 'it' } do
  if_only_exist_node type: 'send', receiver: nil, message: 'should' do
  end
end

Parameters:

  • rules (Hash)

    rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



162
163
164
# File 'lib/synvert/core/rewriter/instance.rb', line 162

def if_only_exist_node(rules, &block)
  Rewriter::IfOnlyExistCondition.new(self, rules, &block).process
end

#insert(code, at: 'end', to: nil) ⇒ Object

Parse insert dsl, it creates a Synvert::Core::Rewriter::InsertAction to insert code.

Examples:

# open('http://test.com')
# =>
# URI.open('http://test.com')
with_node type: 'send', receiver: nil, message: 'open' do
  insert 'URI.', at: 'beginning'
end

Parameters:

  • code (String)

    code need to be inserted.

  • at (String) (defaults to: 'end')

    insert position, beginning or end

  • to (String) (defaults to: nil)

    where to insert, if it is nil, will insert to current node.



215
216
217
# File 'lib/synvert/core/rewriter/instance.rb', line 215

def insert(code, at: 'end', to: nil)
  @current_mutation.insert(@current_node, code, at: at, to: to)
end

#insert_after(code) ⇒ Object

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

Examples:

# Synvert::Application.config.secret_token = "0447aa931d42918bfb934750bb78257088fb671186b5d1b6f9fddf126fc8a14d34f1d045cefab3900751c3da121a8dd929aec9bafe975f1cabb48232b4002e4e"
# =>
# Synvert::Application.config.secret_token = "0447aa931d42918bfb934750bb78257088fb671186b5d1b6f9fddf126fc8a14d34f1d045cefab3900751c3da121a8dd929aec9bafe975f1cabb48232b4002e4e"
# Synvert::Application.config.secret_key_base = "bf4f3f46924ecd9adcb6515681c78144545bba454420973a274d7021ff946b8ef043a95ca1a15a9d1b75f9fbdf85d1a3afaf22f4e3c2f3f78e24a0a188b581df"
with_node type: 'send', message: 'secret_token=' do
  insert_after "{{receiver}}.secret_key_base = \"#{SecureRandom.hex(64)}\""
end

Parameters:

  • code (String)

    code need to be inserted.



230
231
232
# File 'lib/synvert/core/rewriter/instance.rb', line 230

def insert_after(code)
  @current_mutation.insert_after(@current_node, code)
end

#nodeParser::AST::Node

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

Returns:



47
48
49
# File 'lib/synvert/core/rewriter/instance.rb', line 47

def node
  @current_node
end

#prepend(code) ⇒ Object

Parse prepend dsl, it creates a PrependAction to prepend the code to the top of current node body.

Examples:

# def setup
#   do_something
# end
# =>
# def setup
#   super
#   do_something
# end
with_node type: 'def', name: 'setup' do
  prepend 'super'
end

Parameters:

  • code (String)

    code need to be prepended.



200
201
202
# File 'lib/synvert/core/rewriter/instance.rb', line 200

def prepend(code)
  @current_mutation.prepend(@current_node, code)
end

#processObject

Process the instance. It finds specified files, for each file, it executes the block code, rewrites the original code, then write the code back to the original file.



34
35
36
37
38
39
40
41
42
# File 'lib/synvert/core/rewriter/instance.rb', line 34

def process
  @file_patterns.each do |file_pattern|
    Dir.glob(File.join(Configuration.path, file_pattern)).each do |file_path|
      next if Configuration.skip_files.include?(file_path)

      process_file(file_path)
    end
  end
end

#process_with_node(node) { ... } ⇒ Object

Set current_node to node and process.

Parameters:

Yields:

  • process



55
56
57
58
59
# File 'lib/synvert/core/rewriter/instance.rb', line 55

def process_with_node(node)
  self.current_node = node
  yield
  self.current_node = node
end

#process_with_other_node(node) { ... } ⇒ Object

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

Parameters:

Yields:

  • process



65
66
67
68
69
70
# File 'lib/synvert/core/rewriter/instance.rb', line 65

def process_with_other_node(node)
  original_node = current_node
  self.current_node = node
  yield
  self.current_node = original_node
end

#remove(**options) ⇒ Object

Parse remove dsl, it creates a RemoveAction to remove current node.

Examples:

with_node type: 'send', message: { in: %w[puts p] } do
  remove
end

Parameters:

  • options (Hash)

    options.

  • and_comma (Hash)

    a customizable set of options



286
287
288
# File 'lib/synvert/core/rewriter/instance.rb', line 286

def remove(**options)
  @current_mutation.remove(@current_node, **options)
end

#replace(*selectors, with:) ⇒ Object

Parse replace dsl, it creates a ReplaceAction to replace the code of specified child nodes.

Examples:

# assert(object.empty?)
# =>
# assert_empty(object)
with_node type: 'send', receiver: nil, message: 'assert', arguments: { size: 1, first: { type: 'send', message: 'empty?', arguments: { size: 0 } } } do
  replace :message, with: 'assert_empty'
  replace :arguments, with: '{{arguments.first.receiver}}'
end

Parameters:

  • selectors (Array<Symbol>)

    selector names of child node.

  • with (String)

    code need to be replaced with.



275
276
277
# File 'lib/synvert/core/rewriter/instance.rb', line 275

def replace(*selectors, with:)
  @current_mutation.replace(@current_node, *selectors, with: with)
end

#replace_erb_stmt_with_exprObject

Parse replace_erb_stmt_with_expr dsl, it creates a ReplaceErbStmtWithExprAction to replace erb stmt code to expr code.

Examples:

# <% form_for post do |f| %>
# <% end %>
# =>
# <%= form_for post do |f| %>
# <% end %>
with_node type: 'block', caller: { type: 'send', receiver: nil, message: 'form_for' } do
  replace_erb_stmt_with_expr
end


245
246
247
# File 'lib/synvert/core/rewriter/instance.rb', line 245

def replace_erb_stmt_with_expr
  @current_mutation.actions << Rewriter::ReplaceErbStmtWithExprAction.new(@current_node).process
end

#replace_with(code) ⇒ Object

Parse replace_with dsl, it creates a ReplaceWithAction to replace the whole code of current node.

Examples:

# obj.stub(:foo => 1, :bar => 2)
# =>
# allow(obj).to receive_messages(:foo => 1, :bar => 2)
with_node type: 'send', message: 'stub', arguments: { first: { type: 'hash' } } do
  replace_with 'allow({{receiver}}).to receive_messages({{arguments}})'
end

Parameters:

  • code (String)

    code need to be replaced with.



259
260
261
# File 'lib/synvert/core/rewriter/instance.rb', line 259

def replace_with(code)
  @current_mutation.replace_with(@current_node, code)
end

#unless_exist_node(rules, &block) ⇒ Object

Parse unless_exist_node dsl, it creates a UnlessExistCondition to check if matching nodes doesn’t exist in the child nodes, if so, then continue operating on each matching ast node.

Examples:

# obj.stub(:message)
with_node type: 'send', message: 'stub', arguments: { first: { type: { not: 'hash' } } } do
  unless_exist_node type: 'send', message: 'any_instance' do
  end
end

Parameters:

  • rules (Hash)

    rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



147
148
149
# File 'lib/synvert/core/rewriter/instance.rb', line 147

def unless_exist_node(rules, &block)
  Rewriter::UnlessExistCondition.new(self, rules, &block).process
end

#warn(message) ⇒ Object

Parse warn dsl, it creates a Warning to save warning message.

Examples:

within_files 'vendor/plugins' do
  warn 'Rails::Plugin is deprecated and will be removed in Rails 4.0. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies.'
end

Parameters:

  • message (String)

    warning message.



329
330
331
# File 'lib/synvert/core/rewriter/instance.rb', line 329

def warn(message)
  @rewriter.add_warning Rewriter::Warning.new(self, message)
end

#within_node(rules, options = {}) { ... } ⇒ Object Also known as: with_node

Parse within_node dsl, it creates a WithinScope to recursively find matching ast nodes, then continue operating on each matching ast node.

Examples:

# matches User.find_by_login('test')
with_node type: 'send', message: /^find_by_/ do
end

Parameters:

  • rules (Hash)

    rules to find mathing ast nodes.

  • options (Hash) (defaults to: {})

    optional

  • including_self (Hash)

    a customizable set of options

  • stop_at_first_match (Hash)

    a customizable set of options

  • recursive (Hash)

    a customizable set of options

Yields:

  • run on the matching nodes.



103
104
105
# File 'lib/synvert/core/rewriter/instance.rb', line 103

def within_node(rules, options = {}, &block)
  Rewriter::WithinScope.new(self, rules, options, &block).process
end

#wrap(with:) ⇒ Object

Parse wrap dsl, it creates a WrapAction to wrap current node with code.

Examples:

# class Foobar
# end
# =>
# module Synvert
#   class Foobar
#   end
# end
within_node type: 'class' do
  wrap with: 'module Synvert'
end

Parameters:

  • with (String)

    code need to be wrapped with.



319
320
321
# File 'lib/synvert/core/rewriter/instance.rb', line 319

def wrap(with:)
  @current_mutation.wrap(@current_node, with: with)
end