Class: NodeMutation

Inherits:
Object
  • Object
show all
Defined in:
lib/node_mutation.rb,
lib/node_mutation/version.rb

Defined Under Namespace

Classes: Action, Adapter, AppendAction, ConflictActionError, DeleteAction, GroupAction, Helper, IndentAction, InsertAction, InvalidAdapterError, MethodNotSupported, NoopAction, ParserAdapter, PrependAction, PrismAdapter, RemoveAction, ReplaceAction, ReplaceWithAction, Result, Strategy, Struct, SyntaxTreeAdapter

Constant Summary collapse

VERSION =
"1.24.4"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source, adapter:) ⇒ NodeMutation

Initialize a NodeMutation.

Parameters:

  • source (String)

    file source

  • adapter (Symbol)

    :parser or :syntax_tree



69
70
71
72
73
# File 'lib/node_mutation.rb', line 69

def initialize(source, adapter:)
  @source = source
  @actions = []
  @adapter = get_adapter_instance(adapter)
end

Instance Attribute Details

#actionsArray<NodeMutation::Struct::Action> (readonly)

Returns:



32
33
34
# File 'lib/node_mutation.rb', line 32

def actions
  @actions
end

#adapterObject (readonly)

Returns the value of attribute adapter.



32
33
34
# File 'lib/node_mutation.rb', line 32

def adapter
  @adapter
end

#transform_procProc

Returns proc to transfor the actions.

Returns:

  • (Proc)

    proc to transfor the actions



36
37
38
# File 'lib/node_mutation.rb', line 36

def transform_proc
  @transform_proc
end

Class Method Details

.configure(options) ⇒ Object

Configure NodeMutation

Parameters:

  • options (Hash)

    options to configure

Options Hash (options):



43
44
45
46
47
48
49
50
# File 'lib/node_mutation.rb', line 43

def configure(options)
  if options[:strategy]
    @strategy = options[:strategy]
  end
  if options[:tab_width]
    @tab_width = options[:tab_width].to_i
  end
end

.strategyInteger

Get the strategy by default is NodeMutation::Strategy::KEEP_RUNNING

Returns:



55
56
57
# File 'lib/node_mutation.rb', line 55

def strategy
  @strategy ||= Strategy::KEEP_RUNNING
end

.tab_widthInteger

Get tab width

Returns:

  • (Integer)

    tab width, by default is 2



61
62
63
# File 'lib/node_mutation.rb', line 61

def tab_width
  @tab_width ||= 2
end

Instance Method Details

#append(node, code) ⇒ Object

Append code to the ast node. 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

Parameters:

  • node (Node)

    ast node

  • code (String)

    new code to append



90
91
92
# File 'lib/node_mutation.rb', line 90

def append(node, code)
  @actions << AppendAction.new(node, code, adapter: @adapter).process
end

#delete(node, *selectors, and_comma: false) ⇒ Object

Delete source code of the child ast node. 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(...)

Parameters:

  • node (Node)

    ast node

  • selectors (Array<Symbol>)

    selector names of child node.

  • and_comma (Boolean) (defaults to: false)

    delete extra comma.



105
106
107
# File 'lib/node_mutation.rb', line 105

def delete(node, *selectors, and_comma: false)
  @actions << DeleteAction.new(node, *selectors, and_comma: and_comma, adapter: @adapter).process
end

#groupObject

group multiple actions



242
243
244
245
246
247
248
249
# File 'lib/node_mutation.rb', line 242

def group
  current_actions = @actions
  group_action = GroupAction.new
  @actions = group_action.actions
  yield
  @actions = current_actions
  @actions << group_action.process
end

#indent(node) ⇒ Object

Indent source code of the ast node source code of ast node is

class Foobar
end

then we call

indent(node)

the source code will be rewritten to

class Foobar
end

Parameters:

  • node (Node)

    ast node



231
232
233
# File 'lib/node_mutation.rb', line 231

def indent(node)
  @actions << IndentAction.new(node, adapter: @adapter).process
end

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

Insert code to the ast node. 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')

Parameters:

  • node (Node)

    ast node

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

  • and_comma (Boolean) (defaults to: false)

    insert extra comma.



122
123
124
# File 'lib/node_mutation.rb', line 122

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

#noop(node) ⇒ Object

No operation.

Parameters:

  • node (Node)

    ast node



237
238
239
# File 'lib/node_mutation.rb', line 237

def noop(node)
  @actions << NoopAction.new(node, adapter: @adapter).process
end

#prepend(node, code) ⇒ Object

Prepend code to the ast node. 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

Parameters:

  • node (Node)

    ast node

  • code (String)

    new code to prepend.



141
142
143
# File 'lib/node_mutation.rb', line 141

def prepend(node, code)
  @actions << PrependAction.new(node, code, adapter: @adapter).process
end

#processNodeMutation::Result

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 }` if strategy is set to KEEP_RUNNING.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/node_mutation.rb', line 258

def process
  @actions = optimize_group_actions(@actions)

  flatten_actions = flat_actions(@actions)
  if flatten_actions.length == 0
    return NodeMutation::Result.new(affected: false, conflicted: false)
  end

  @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)
  result = NodeMutation::Result.new(affected: true, conflicted: !conflict_actions.empty?)
  result.new_source = new_source
  result
end

#remove(node, and_comma: false) ⇒ Object

Remove source code of the ast node. source code of the ast node is

puts "test"

then we call

mutation.remove(node)

the source code will be removed

Parameters:

  • node (Node)

    ast node

  • and_comma (Boolean) (defaults to: false)

    delete extra comma.



154
155
156
# File 'lib/node_mutation.rb', line 154

def remove(node, and_comma: false)
  @actions << RemoveAction.new(node, and_comma: and_comma, adapter: @adapter).process
end

#replace(node, *selectors, with:) ⇒ Object

Replace child node of the ast node with new code. 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)

Parameters:

  • node (Node)

    ast node

  • selectors (Array<Symbol>)

    selector names of child node.

  • with (String)

    code need to be replaced with.



170
171
172
# File 'lib/node_mutation.rb', line 170

def replace(node, *selectors, with:)
  @actions << ReplaceAction.new(node, *selectors, with: with, adapter: @adapter).process
end

#replace_with(node, code) ⇒ Object

Replace source code of the ast node with new code. 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)

Parameters:

  • node (Node)

    ast node

  • code (String)

    code need to be replaced with.



184
185
186
# File 'lib/node_mutation.rb', line 184

def replace_with(node, code)
  @actions << ReplaceWithAction.new(node, code, adapter: @adapter).process
end

#testNodeMutation::Result

Test actions and return the actions.

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 }` if strategy is set to KEEP_RUNNING.



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/node_mutation.rb', line 287

def test
  @actions = optimize_group_actions(@actions)

  flatten_actions = flat_actions(@actions)
  if flatten_actions.length == 0
    return NodeMutation::Result.new(affected: false, conflicted: false)
  end

  @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

  result = NodeMutation::Result.new(affected: true, conflicted: !conflict_actions.empty?)
  actions = sort_actions(get_filter_actions(conflict_actions))
  result.actions = actions.map(&:to_struct)
  result
end

#wrap(node, prefix:, suffix:, newline: false) ⇒ Object

Wrap source code of the ast node with prefix and suffix code. 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

Parameters:

  • node (Node)

    ast node

  • prefix (String)

    prefix code need to be wrapped with.

  • suffix (String)

    suffix code need to be wrapped with.

  • newline (Boolean) (defaults to: false)

    add newline after prefix and before suffix.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/node_mutation.rb', line 204

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