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
@!attribute [rw] current_node
@return current parsing node
@!attribute [rw] current_file
@return current filename
@!attribute [rw] current_node
@return current parsing node
@!attribute [rw] current_file
@return current filename
Public Class Methods
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
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
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 cached file source and ast.
# File lib/synvert/core/rewriter/instance.rb, line 55 def reset @file_source = {} @file_ast = {} end
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 but nil.
# File lib/synvert/core/rewriter/instance.rb, line 304 def any_value Rewriter::AnyValue.new end
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
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
Current file source
# File lib/synvert/core/rewriter/instance.rb, line 68 def file_source self.class.file_source(current_file) end
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
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
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
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
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
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
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 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
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
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
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
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
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
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
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
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
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
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
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
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
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