Class: NodeMutation

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

Defined Under Namespace

Modules: Engine Classes: Action, Adapter, AppendAction, ConflictActionError, DeleteAction, InsertAction, MethodNotSupported, NoopAction, ParserAdapter, PrependAction, RemoveAction, ReplaceAction, ReplaceWithAction, Result, Strategy, WrapAction

Constant Summary collapse

VERSION =
"1.9.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source) ⇒ NodeMutation

Initialize a NodeMutation.

Parameters:

  • source (String)

    file source



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

def initialize(source)
  @source = source
  @actions = []
end

Instance Attribute Details

#actionsObject (readonly)

Returns the value of attribute actions.



26
27
28
# File 'lib/node_mutation.rb', line 26

def actions
  @actions
end

Class Method Details

.adapterNodeMutation::Adapter

Get the adapter

Returns:



48
49
50
# File 'lib/node_mutation.rb', line 48

def adapter
  @adapter ||= ParserAdapter.new
end

.configure(options) ⇒ Object

Configure NodeMutation

Parameters:

  • options (Hash)

    options to configure

Options Hash (options):



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/node_mutation.rb', line 34

def configure(options)
  if options[:adapter]
    @adapter = options[:adapter]
  end
  if options[:strategy]
    @strategy = options[:strategy]
  end
  if options[:tab_width]
    @tab_width = options[:tab_width]
  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



88
89
90
# File 'lib/node_mutation.rb', line 88

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

#delete(node, *selectors, **options) ⇒ 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.

  • options (Hash)
  • and_comma (Hash)

    a customizable set of options



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

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

#insert(node, code, at: 'end', to: nil) ⇒ 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.



120
121
122
# File 'lib/node_mutation.rb', line 120

def insert(node, code, at: 'end', to: nil)
  @actions << InsertAction.new(node, code, at: at, to: to).process
end

#noop(node) ⇒ Object

No operation.

Parameters:

  • node (Node)

    ast node



207
208
209
# File 'lib/node_mutation.rb', line 207

def noop(node)
  @actions << NoopAction.new(node).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.



139
140
141
# File 'lib/node_mutation.rb', line 139

def prepend(node, code)
  @actions << PrependAction.new(node, code).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.



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/node_mutation.rb', line 218

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

  conflict_actions = []
  source = +@source
  @actions.sort_by! { |action| [action.start, action.end] }
  conflict_actions = get_conflict_actions
  if conflict_actions.size > 0 && strategy?(Strategy::THROW_ERROR)
    raise ConflictActionError, "mutation actions are conflicted"
  end
  @actions.reverse_each do |action|
    source[action.start...action.end] = action.new_code if action.new_code
  end
  NodeMutation::Result.new(
    affected: true,
    conflicted: !conflict_actions.empty?,
    new_source: source
  )
end

#remove(node, **options) ⇒ 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

  • options (Hash)

    options.

  • and_comma (Hash)

    a customizable set of options



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

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



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

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



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

def replace_with(node, code)
  @actions << ReplaceWithAction.new(node, code).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.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/node_mutation.rb', line 247

def test
  if @actions.length == 0
    return NodeMutation::Result.new(affected: false, conflicted: false, actions: [])
  end

  conflict_actions = []
  @actions.sort_by! { |action| [action.start, action.end] }
  conflict_actions = get_conflict_actions
  if conflict_actions.size > 0 && strategy?(Strategy::THROW_ERROR)
    raise ConflictActionError, "mutation actions are conflicted"
  end
  NodeMutation::Result.new(
    affected: true,
    conflicted: !conflict_actions.empty?,
    actions: format_actions(@actions)
  )
end

#wrap(node, with:) ⇒ Object

Wrap source code of the ast node with new code. source code of the ast node is

class Foobar
end

then we call

wrap(node, with: 'module Synvert')

the source code will be rewritten to

module Synvert
  class Foobar
  end
end

Parameters:

  • node (Node)

    ast node

  • with (String)

    code need to be wrapped with.



201
202
203
# File 'lib/node_mutation.rb', line 201

def wrap(node, with:)
  @actions << WrapAction.new(node, with: with).process
end