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.



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)



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



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

def transform_proc
  @transform_proc
end

Class Method Details

.configure(options) ⇒ Object

Configure NodeMutation

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



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

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

.tab_widthInteger

Get tab width



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


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


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


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')


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.



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


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



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)


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)


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


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