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.



17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/synvert/core/rewriter/instance.rb', line 17

def initialize(rewriter, file_patterns, &block)
  @rewriter = rewriter
  @actions = []
  @file_patterns = file_patterns
  @block = block
  strategy = NodeMutation::Strategy::KEEP_RUNNING
  if rewriter.options[:strategy] == Strategy::ALLOW_INSERT_AT_SAME_POSITION
    strategy |=  NodeMutation::Strategy::ALLOW_INSERT_AT_SAME_POSITION
  end
  NodeMutation.configure({ strategy: strategy })
  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



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

attr_accessor :current_node, :current_file, :current_mutation

#current_mutationObject

Returns current mutation.

Returns:

  • current mutation



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

attr_accessor :current_node, :current_file, :current_mutation

#current_nodeObject

Returns current parsing node.

Returns:

  • current parsing node



36
37
38
# File 'lib/synvert/core/rewriter/instance.rb', line 36

def current_node
  @current_node
end

Instance Method Details

#any_valueNodeQuery::AnyValue

Match any value but nil.

Examples:

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

Returns:

  • (NodeQuery::AnyValue)


360
361
362
# File 'lib/synvert/core/rewriter/instance.rb', line 360

def any_value
  NodeQuery::AnyValue.new
end

#append(code) ⇒ Object

It appends 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.



189
190
191
# File 'lib/synvert/core/rewriter/instance.rb', line 189

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

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

It deletes 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



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

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

#goto_node(child_node_name, &block) ⇒ Object

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.



128
129
130
# File 'lib/synvert/core/rewriter/instance.rb', line 128

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

#if_exist_node(nql_or_rules, &block) ⇒ Object

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:

  • nql_or_rules (String|Hash)

    nql or rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



142
143
144
# File 'lib/synvert/core/rewriter/instance.rb', line 142

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

#if_only_exist_node(nql_or_rules, &block) ⇒ Object

It creates a Synvert::Core::Rewriter::IfOnlyExistCondition to check if current node has only one child node and the child node matches, 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:

  • nql_or_rules (String|Hash)

    nql or rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



171
172
173
# File 'lib/synvert/core/rewriter/instance.rb', line 171

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

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

It inserts 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.



222
223
224
# File 'lib/synvert/core/rewriter/instance.rb', line 222

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

#insert_after(code, to: nil) ⇒ Object

It inserts 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.



236
237
238
239
# File 'lib/synvert/core/rewriter/instance.rb', line 236

def insert_after(code, to: nil)
  column = ' ' * NodeMutation.adapter.get_start_loc(@current_node).column
  @current_mutation.insert(@current_node, "\n#{column}#{code}", at: 'end', to: to)
end

#insert_before(code, to: nil) ⇒ Object

It inserts the code previous to the current node.

Examples:

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

Parameters:

  • code (String)

    code need to be inserted.



251
252
253
254
# File 'lib/synvert/core/rewriter/instance.rb', line 251

def insert_before(code, to: nil)
  column = ' ' * NodeMutation.adapter.get_start_loc(@current_node).column
  @current_mutation.insert(@current_node, "#{code}\n#{column}", at: 'beginning', to: to)
end

#nodeParser::AST::Node

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

Returns:



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

def node
  @current_node
end

#noopObject

No operation.



342
343
344
# File 'lib/synvert/core/rewriter/instance.rb', line 342

def noop
  @current_mutation.noop(@current_node)
end

#prepend(code) ⇒ Object

It prepends 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.



207
208
209
# File 'lib/synvert/core/rewriter/instance.rb', line 207

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 writes the code back to the original file.



41
42
43
44
45
# File 'lib/synvert/core/rewriter/instance.rb', line 41

def process
  get_file_paths.each do |file_path|
    process_file(file_path)
  end
end

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

Set current_node to node and process.

Parameters:

Yields:

  • process



73
74
75
76
77
# File 'lib/synvert/core/rewriter/instance.rb', line 73

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



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

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

#remove(**options) ⇒ Object

It removes 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



305
306
307
# File 'lib/synvert/core/rewriter/instance.rb', line 305

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

#replace(*selectors, with:) ⇒ Object

It replaces 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.



294
295
296
# File 'lib/synvert/core/rewriter/instance.rb', line 294

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

#replace_erb_stmt_with_exprObject

It replaces 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


266
267
268
# File 'lib/synvert/core/rewriter/instance.rb', line 266

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

#replace_with(code) ⇒ Object

It replaces 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.



279
280
281
# File 'lib/synvert/core/rewriter/instance.rb', line 279

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

#testObject

Test the instance. It finds specified files, for each file, it executes the block code, tests the original code, then returns the actions.



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/synvert/core/rewriter/instance.rb', line 50

def test
  if Configuration.number_of_workers > 1
    Parallel.map(get_file_paths, in_processes: Configuration.number_of_workers) do |file_path|
      test_file(file_path)
    end
  else
    get_file_paths.map do |file_path|
      test_file(file_path)
    end
  end
end

#unless_exist_node(nql_or_rules, &block) ⇒ Object

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:

  • nql_or_rules (String|Hash)

    nql or rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



156
157
158
# File 'lib/synvert/core/rewriter/instance.rb', line 156

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

#warn(message) ⇒ Object

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.



352
353
354
# File 'lib/synvert/core/rewriter/instance.rb', line 352

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

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

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
# matches FactoryBot.create(:user)
with_node '.send[receiver=FactoryBot][message=create][arguments.size=1]' do
end

Parameters:

  • nql_or_rules (String|Hash)

    nql or 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.



109
110
111
112
113
# File 'lib/synvert/core/rewriter/instance.rb', line 109

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

#wrap(with:) ⇒ Object

It wraps 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.



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

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