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 contains one or many [Synvert::Core::Rewriter::Scope] and [Synvert::Rewriter::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, &block) ⇒ Synvert::Core::Rewriter::Instance

Initialize an instance.

Parameters:

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

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

  • block (Block)

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



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

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



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

attr_accessor :current_node, :current_file

#current_nodeObject

Returns current parsing node.

Returns:

  • current parsing node



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

def current_node
  @current_node
end

Class Method Details

.file_ast(file_path) ⇒ String

Cached file ast.

Parameters:

  • file_path (String)

    file path

Returns:

  • (String)

    ast node for file



30
31
32
33
34
35
36
37
38
39
40
41
# 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) ⇒ String

Cached file source.

Parameters:

  • file_path (String)

    file path

Returns:

  • (String)

    file source



16
17
18
19
20
21
22
23
24
# 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

.resetObject

Reset cached file source and ast.



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

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



47
48
49
50
51
52
# 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

Instance Method Details

#any_valueObject

Any value but nil.



307
308
309
# File 'lib/synvert/core/rewriter/instance.rb', line 307

def any_value
  Rewriter::AnyValue.new
end

#append(code) ⇒ Object

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

Parameters:

  • code (String)

    code need to be appended.



226
227
228
# File 'lib/synvert/core/rewriter/instance.rb', line 226

def append(code)
  @actions << Rewriter::AppendAction.new(self, code).process
end

#delete(*selectors) ⇒ Object

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

Parameters:

  • selectors (Array<Symbol>)

    selector names of child node.



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

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

#file_sourceObject

Current file source



68
69
70
# 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) ⇒ Object

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

Parameters:

  • child_node_name (Symbol|String)

    the name of the child nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



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

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.

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

Parameters:

  • rules (Hash)

    rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



218
219
220
# File 'lib/synvert/core/rewriter/instance.rb', line 218

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

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

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

Parameters:

  • code (String)

    code need to be inserted.

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

    insert position, beginning or end, end is the default.



243
244
245
# File 'lib/synvert/core/rewriter/instance.rb', line 243

def insert(code, at: 'end')
  @actions << Rewriter::InsertAction.new(self, code, at: at).process
end

#insert_after(node) ⇒ Object

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

Parameters:

  • code (String)

    code need to be inserted.



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

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

#nodeParser::AST::Node

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

Returns:



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

def node
  @current_node
end

#prepend(code) ⇒ Object

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

Parameters:

  • code (String)

    code need to be prepended.



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

def prepend(code)
  @actions << Rewriter::PrependAction.new(self, code).process
end

#processObject

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.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/synvert/core/rewriter/instance.rb', line 89

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

      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
end

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

Set current_node to node and process.

Parameters:

Yields:

  • process



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

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



150
151
152
153
154
155
# File 'lib/synvert/core/rewriter/instance.rb', line 150

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

#removeObject

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



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

def remove
  @actions << Rewriter::RemoveAction.new(self).process
end

#replace(*selectors, with:) ⇒ Object

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

Parameters:

  • selectors (Array<Symbol>)

    selector names of child node.

  • with (String)

    code need to be replaced with.



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

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 [Synvert::Core::Rewriter::ReplaceErbStmtWithExprAction] to replace erb stmt code to expr code.



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

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

#replace_with(code) ⇒ Object

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

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

#unless_exist_node(rules, &block) ⇒ Object

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.

Parameters:

  • rules (Hash)

    rules to check mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



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

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

#warn(message) ⇒ Object

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

Parameters:

  • message (String)

    warning message.



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

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

#within_direct_node(rules, &block) ⇒ Object Also known as: with_direct_node

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.

Parameters:

  • rules (Hash)

    rules to find mathing ast nodes.

  • block (Block)

    block code to continue operating on the matching nodes.



179
180
181
# File 'lib/synvert/core/rewriter/instance.rb', line 179

def within_direct_node(rules, &block)
  Rewriter::WithinScope.new(self, rules, { direct: true }, &block).process
end

#within_node(rules, options = nil, &block) ⇒ Object Also known as: with_node

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.

Parameters:

  • rules (Hash)

    rules to find mathing ast nodes.

  • options (Hash) (defaults to: nil)

    optional, set if stop_when_match or not.

  • block (Block)

    block code to continue operating on the matching nodes.



167
168
169
170
# File 'lib/synvert/core/rewriter/instance.rb', line 167

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

#wrap(with:, indent: nil) ⇒ Object

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

Parameters:

  • with (String)

    code need to be wrapped with.

  • indent (Integer) (defaults to: nil)

    number of whitespaces.



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

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