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

Class Method 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:

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


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

attr_accessor :current_node, :current_file

#current_nodeObject

Returns current parsing node.

Returns:

  • current parsing node


77
78
79
# File 'lib/synvert/core/rewriter/instance.rb', line 77

def current_node
  @current_node
end

Class Method Details

.file_ast(file_path) ⇒ String

Get file ast.

Parameters:

  • file_path (String)

    file path

Returns:

  • (String)

    ast node for file


42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/synvert/core/rewriter/instance.rb', line 42

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) ⇒ String

Get file source.

Parameters:

  • file_path (String)

    file path

Returns:

  • (String)

    file source


28
29
30
31
32
33
34
35
36
# File 'lib/synvert/core/rewriter/instance.rb', line 28

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

.resetObject

Reset file source and ast.


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

def reset
  @file_source = {}
  @file_ast = {}
end

.write_file(file_path, source) ⇒ Object

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

Parameters:

  • file_path (String)

    file path

  • source (String)

    file source


59
60
61
62
63
64
# File 'lib/synvert/core/rewriter/instance.rb', line 59

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

Instance Method Details

#any_valueSynvert::Core::Rewriter::AnyValue

Match any value but nil.

Examples:

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

Returns:


390
391
392
# File 'lib/synvert/core/rewriter/instance.rb', line 390

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.


233
234
235
# File 'lib/synvert/core/rewriter/instance.rb', line 233

def append(code)
  @actions << Rewriter::AppendAction.new(self, code).process
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


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

def delete(*selectors, **options)
  @actions << Rewriter::DeleteAction.new(self, *selectors, **options).process
end

#file_sourceObject

Current file source


80
81
82
# File 'lib/synvert/core/rewriter/instance.rb', line 80

def file_source
  self.class.file_source(current_file)
end

#find_node(query_string) { ... } ⇒ 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:

  • query_string (String)

    query string to find matching ast nodes.

Yields:

  • run on the matching nodes.

Raises:


138
139
140
# File 'lib/synvert/core/rewriter/instance.rb', line 138

def find_node(query_string, &block)
  Rewriter::QueryScope.new(self, query_string, &block).process
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.


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

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.


185
186
187
# File 'lib/synvert/core/rewriter/instance.rb', line 185

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.


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

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.


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

def insert(code, at: 'end', to: nil)
  @actions << Rewriter::InsertAction.new(self, code, at: at, to: to).process
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.


282
283
284
# File 'lib/synvert/core/rewriter/instance.rb', line 282

def insert_after(code)
  @actions << Rewriter::InsertAfterAction.new(self, code).process
end

#nodeParser::AST::Node

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

Returns:


100
101
102
# File 'lib/synvert/core/rewriter/instance.rb', line 100

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.


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

def prepend(code)
  @actions << Rewriter::PrependAction.new(self, code).process
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.


87
88
89
90
91
92
93
94
95
# File 'lib/synvert/core/rewriter/instance.rb', line 87

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


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

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


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

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


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

def remove(**options)
  @actions << Rewriter::RemoveAction.new(self, **options).process
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.


312
313
314
# File 'lib/synvert/core/rewriter/instance.rb', line 312

def replace(*selectors, with:)
  @actions << Rewriter::ReplaceAction.new(self, *selectors, with: with).process
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

327
328
329
# File 'lib/synvert/core/rewriter/instance.rb', line 327

def replace_erb_stmt_with_expr
  @actions << Rewriter::ReplaceErbStmtWithExprAction.new(self).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.


296
297
298
# File 'lib/synvert/core/rewriter/instance.rb', line 296

def replace_with(code)
  @actions << Rewriter::ReplaceWithAction.new(self, code).process
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.


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

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.


382
383
384
# File 'lib/synvert/core/rewriter/instance.rb', line 382

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

  • stop_when_match (Hash)

    a customizable set of options

  • direct (Hash)

    a customizable set of options

Yields:

  • run on the matching nodes.


153
154
155
156
157
# File 'lib/synvert/core/rewriter/instance.rb', line 153

def within_node(rules, options = {}, &block)
  options[:stop_when_match] ||= false
  options[:direct] ||= false
  Rewriter::WithinScope.new(self, rules, options, &block).process
end

#wrap(with:, indent: nil) ⇒ 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.

  • indent (Integer, nil) (defaults to: nil)

    number of whitespaces.


372
373
374
# File 'lib/synvert/core/rewriter/instance.rb', line 372

def wrap(with:, indent: nil)
  @actions << Rewriter::WrapAction.new(self, with: with, indent: indent).process
end