lib/node_mutation.rb in node_mutation-1.22.2 vs lib/node_mutation.rb in node_mutation-1.22.3

- old
+ new

@@ -5,10 +5,11 @@ class NodeMutation class MethodNotSupported < StandardError; end class ConflictActionError < StandardError; end class InvalidAdapterError < StandardError; end + autoload :Actionable, "node_mutation/actionable" autoload :Adapter, "node_mutation/adapter" autoload :ParserAdapter, "node_mutation/adapter/parser" autoload :SyntaxTreeAdapter, "node_mutation/adapter/syntax_tree" autoload :Action, 'node_mutation/action' autoload :AppendAction, 'node_mutation/action/append_action' @@ -24,10 +25,12 @@ autoload :Result, 'node_mutation/result' autoload :Strategy, 'node_mutation/strategy' autoload :Struct, 'node_mutation/struct' autoload :Helper, 'node_mutation/helper' + include Actionable + # @!attribute [r] actions # @return [Array<NodeMutation::Struct::Action>] attr_reader :actions, :adapter # @!attribute [rw] transform_proc @@ -69,186 +72,10 @@ @source = source @actions = [] @adapter = get_adapter_instance(adapter) end - # Append code to the ast node. - # @param node [Node] ast node - # @param code [String] new code to append - # @example - # source code of the ast node is - # def teardown - # clean_something - # end - # then we call - # mutation.append(node, 'super') - # the source code will be rewritten to - # def teardown - # clean_something - # super - # end - def append(node, code) - @actions << AppendAction.new(node, code, adapter: @adapter).process - end - - # Delete source code of the child ast node. - # @param node [Node] ast node - # @param selectors [Array<Symbol>] selector names of child node. - # @param and_comma [Boolean] delete extra comma. - # @example - # source code of the ast node is - # FactoryBot.create(...) - # then we call - # mutation.delete(node, :receiver, :dot) - # the source code will be rewritten to - # create(...) - def delete(node, *selectors, and_comma: false) - @actions << DeleteAction.new(node, *selectors, and_comma: and_comma, adapter: @adapter).process - end - - # Insert code to the ast node. - # @param node [Node] ast node - # @param code [String] code need to be inserted. - # @param at [String] insert position, beginning or end - # @param to [String] where to insert, if it is nil, will insert to current node. - # @param and_comma [Boolean] insert extra comma. - # @example - # source code of the ast node is - # open('http://test.com') - # then we call - # mutation.insert(node, 'URI.', at: 'beginning') - # the source code will be rewritten to - # URI.open('http://test.com') - def insert(node, code, at: 'end', to: nil, and_comma: false) - @actions << InsertAction.new(node, code, at: at, to: to, and_comma: and_comma, adapter: @adapter).process - end - - # Prepend code to the ast node. - # @param node [Node] ast node - # @param code [String] new code to prepend. - # @example - # source code of the ast node is - # def setup - # do_something - # end - # then we call - # mutation.prepend(node, 'super') - # the source code will be rewritten to - # def setup - # super - # do_something - # end - def prepend(node, code) - @actions << PrependAction.new(node, code, adapter: @adapter).process - end - - # Remove source code of the ast node. - # @param node [Node] ast node - # @param and_comma [Boolean] delete extra comma. - # @example - # source code of the ast node is - # puts "test" - # then we call - # mutation.remove(node) - # the source code will be removed - def remove(node, and_comma: false) - @actions << RemoveAction.new(node, and_comma: and_comma, adapter: @adapter).process - end - - # Replace child node of the ast node with new code. - # @param node [Node] ast node - # @param selectors [Array<Symbol>] selector names of child node. - # @param with [String] code need to be replaced with. - # @example - # source code of the ast node is - # assert(object.empty?) - # then we call - # mutation.replace(node, :message, with: 'assert_empty') - # mutation.replace(node, :arguments, with: '{{arguments.first.receiver}}') - # the source code will be rewritten to - # assert_empty(object) - def replace(node, *selectors, with:) - @actions << ReplaceAction.new(node, *selectors, with: with, adapter: @adapter).process - end - - # Replace source code of the ast node with new code. - # @param node [Node] ast node - # @param code [String] code need to be replaced with. - # @example - # source code of the ast node is - # obj.stub(:foo => 1, :bar => 2) - # then we call - # replace_with 'allow({{receiver}}).to receive_messages({{arguments}})' - # the source code will be rewritten to - # allow(obj).to receive_messages(:foo => 1, :bar => 2) - def replace_with(node, code) - @actions << ReplaceWithAction.new(node, code, adapter: @adapter).process - end - - # Wrap source code of the ast node with prefix and suffix code. - # @param node [Node] ast node - # @param prefix [String] prefix code need to be wrapped with. - # @param suffix [String] suffix code need to be wrapped with. - # @param newline [Boolean] add newline after prefix and before suffix. - # @example - # source code of the ast node is - # class Foobar - # end - # then we call - # wrap(node, prefix: 'module Synvert', suffix: 'end', newline: true) - # the source code will be rewritten to - # module Synvert - # class Foobar - # end - # end - def wrap(node, prefix:, suffix:, newline: false) - if newline - indentation = @adapter.get_start_loc(node).column - group do - insert node, prefix + "\n" + (' ' * indentation), at: 'beginning' - insert node, "\n" + (' ' * indentation) + suffix, at: 'end' - indent node - end - else - group do - insert node, prefix, at: 'beginning' - insert node, suffix, at: 'end' - end - end - end - - # Indent source code of the ast node - # @param node [Node] ast node - # @example - # source code of ast node is - # class Foobar - # end - # then we call - # indent(node) - # the source code will be rewritten to - # class Foobar - # end - def indent(node) - @actions << IndentAction.new(node, adapter: @adapter).process - end - - # No operation. - # @param node [Node] ast node - def noop(node) - @actions << NoopAction.new(node, adapter: @adapter).process - end - - # group multiple actions - def group - current_actions = @actions - group_action = GroupAction.new - @actions = group_action.actions - yield - @actions = current_actions - @actions << group_action.process - end - # Process actions and return the new source. # # If there's an action range conflict, # it will raise a ConflictActionError if strategy is set to THROW_ERROR, # it will process all non conflicted actions and return `{ conflict: true }` @@ -260,19 +87,18 @@ flatten_actions = flat_actions(@actions) if flatten_actions.length == 0 return NodeMutation::Result.new(affected: false, conflicted: false) end - source = +@source @transform_proc.call(@actions) if @transform_proc sorted_actions = sort_flatten_actions(flatten_actions) conflict_actions = get_conflict_actions(sorted_actions) if conflict_actions.size > 0 && strategy?(Strategy::THROW_ERROR) raise ConflictActionError, "mutation actions are conflicted" end actions = sort_flatten_actions(flat_actions(get_filter_actions(conflict_actions))) - new_source = rewrite_source(source, actions) + new_source = rewrite_source(+@source, actions) result = NodeMutation::Result.new(affected: true, conflicted: !conflict_actions.empty?) result.new_source = new_source result end