node_mutation 1.19.4 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9058cbdb6c979c921d77499277354922b7488da6315b85b368c8f0c14b7bf27e
4
- data.tar.gz: 9f64d3bdb87aa025cf6362127a1170a014f28ec2ec7e04d88756bcd50744429e
3
+ metadata.gz: fd49bceca35429738e6137151b99634e0ef83801214ba58e80c5e1d67b299b07
4
+ data.tar.gz: ef1f95f7edcf89b73b3eeb11137e374c813460cd4c5c8c67b970a5b29acc4e47
5
5
  SHA512:
6
- metadata.gz: c3d8f491d11179cd203167ef7a76b87ea16fdfb0f2454afeb0600d4fb97147a9e237d2f1b86f8432643971b4a00016b1d143813572c49eba0e1d58b58f7c6f81
7
- data.tar.gz: e1c15e3f180186f28486cc14f3d4ebd0ee132c22d9128058e709d545b2c7bb7dae13529c7b74163427e3bfc3f0bb80eb3b73e74b34e4847ae3c0d0dd955d40e1
6
+ metadata.gz: 71f84423b7cc2edd182ce760c984c1fef78f17ca267c72fb02f2eea9674dad5cf7c284180e03dbb888b5872e1157110e656d1f3587df17fe72140110594f0665
7
+ data.tar.gz: 951e9c6ba7afb239dab12428b40509e7d66178fe4776a317ebe5757a8d142e4fabe59ed1e75c5bedc4fe6a08e531c5176112cd48131b91422d1366e9d36af89a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # NodeMutation
2
2
 
3
+ ## 1.20.0 (2023-09-24)
4
+
5
+ * Add `CombinedAction` to combine multiple actions.
6
+ * Add `combine` dsl to combine multiple actions.
7
+ * Add `NodeMutation::Helper.iterate_actions`
8
+
3
9
  ## 1.19.4 (2023-08-17)
4
10
 
5
11
  * Use `NodeMutation.adapter.get_indent`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.19.4)
4
+ node_mutation (1.20.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CombinedAction combines multiple actions.
4
+ class NodeMutation::CombinedAction < NodeMutation::Action
5
+ DEFAULT_START = 2**30
6
+
7
+ attr_accessor :actions
8
+
9
+ def initialize
10
+ @actions = []
11
+ @type = :combined
12
+ end
13
+
14
+ def new_code
15
+ nil
16
+ end
17
+
18
+ private
19
+
20
+ # Calculate the begin and end positions.
21
+ def calculate_position
22
+ @start = DEFAULT_START
23
+ @end = 0
24
+ NodeMutation::Helper.iterate_actions(@actions) do |action|
25
+ @start = [action.start, @start].min
26
+ @end = [action.end, @end].max
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class NodeMutation::Helper
4
+ # It iterates over all actions, and calls the given block with each action.
5
+ def self.iterate_actions(actions, &block)
6
+ actions.each do |action|
7
+ if action.is_a?(NodeMutation::CombinedAction)
8
+ iterate_actions(action.actions, &block)
9
+ else
10
+ block.call(action)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeMutation
4
- VERSION = "1.19.4"
4
+ VERSION = "1.20.0"
5
5
  end
data/lib/node_mutation.rb CHANGED
@@ -11,6 +11,7 @@ class NodeMutation
11
11
  autoload :SyntaxTreeAdapter, "node_mutation/adapter/syntax_tree"
12
12
  autoload :Action, 'node_mutation/action'
13
13
  autoload :AppendAction, 'node_mutation/action/append_action'
14
+ autoload :CombinedAction, 'node_mutation/action/combined_action'
14
15
  autoload :DeleteAction, 'node_mutation/action/delete_action'
15
16
  autoload :IndentAction, 'node_mutation/action/indent_action'
16
17
  autoload :InsertAction, 'node_mutation/action/insert_action'
@@ -22,6 +23,7 @@ class NodeMutation
22
23
  autoload :Result, 'node_mutation/result'
23
24
  autoload :Strategy, 'node_mutation/strategy'
24
25
  autoload :Struct, 'node_mutation/struct'
26
+ autoload :Helper, 'node_mutation/helper'
25
27
 
26
28
  # @!attribute [r] actions
27
29
  # @return [Array<NodeMutation::Struct::Action>]
@@ -208,21 +210,50 @@ class NodeMutation
208
210
  def wrap(node, prefix:, suffix:, newline: false)
209
211
  if newline
210
212
  indentation = NodeMutation.adapter.get_start_loc(node).column
211
- @actions << InsertAction.new(node, prefix + "\n" + (' ' * indentation), at: 'beginning').process
212
- @actions << InsertAction.new(node, "\n" + (' ' * indentation) + suffix, at: 'end').process
213
- @actions << IndentAction.new(node).process
213
+ combine do
214
+ insert node, prefix + "\n" + (' ' * indentation), at: 'beginning'
215
+ insert node, "\n" + (' ' * indentation) + suffix, at: 'end'
216
+ indent node
217
+ end
214
218
  else
215
- @actions << InsertAction.new(node, prefix, at: 'beginning').process
216
- @actions << InsertAction.new(node, suffix, at: 'end').process
219
+ combine do
220
+ insert node, prefix, at: 'beginning'
221
+ insert node, suffix, at: 'end'
222
+ end
217
223
  end
218
224
  end
219
225
 
226
+ # Indent source code of the ast node
227
+ # @param node [Node] ast node
228
+ # @example
229
+ # source code of ast node is
230
+ # class Foobar
231
+ # end
232
+ # then we call
233
+ # indent(node)
234
+ # the source code will be rewritten to
235
+ # class Foobar
236
+ # end
237
+ def indent(node)
238
+ @actions << IndentAction.new(node).process
239
+ end
240
+
220
241
  # No operation.
221
242
  # @param node [Node] ast node
222
243
  def noop(node)
223
244
  @actions << NoopAction.new(node).process
224
245
  end
225
246
 
247
+ # Combine multiple actions
248
+ def combine
249
+ current_actions = @actions
250
+ combined_action = CombinedAction.new
251
+ @actions = combined_action.actions
252
+ yield
253
+ @actions = current_actions
254
+ @actions << combined_action.process
255
+ end
256
+
226
257
  # Process actions and return the new source.
227
258
  #
228
259
  # If there's an action range conflict,
@@ -231,23 +262,22 @@ class NodeMutation
231
262
  # if strategy is set to KEEP_RUNNING.
232
263
  # @return {NodeMutation::Result}
233
264
  def process
265
+ @actions = flatten_actions(@actions)
234
266
  if @actions.length == 0
235
267
  return NodeMutation::Result.new(affected: false, conflicted: false)
236
268
  end
237
269
 
238
270
  source = +@source
239
271
  @transform_proc.call(@actions) if @transform_proc
240
- @actions.sort_by! { |action| [action.start, action.end] }
241
- conflict_actions = get_conflict_actions
272
+ sort_actions!(@actions)
273
+ conflict_actions = get_conflict_actions(@actions)
242
274
  if conflict_actions.size > 0 && strategy?(Strategy::THROW_ERROR)
243
275
  raise ConflictActionError, "mutation actions are conflicted"
244
276
  end
245
277
 
246
- @actions.reverse_each do |action|
247
- source[action.start...action.end] = action.new_code if action.new_code
248
- end
278
+ new_source = rewrite_source(source, @actions)
249
279
  result = NodeMutation::Result.new(affected: true, conflicted: !conflict_actions.empty?)
250
- result.new_source = source
280
+ result.new_source = new_source
251
281
  result
252
282
  end
253
283
 
@@ -259,13 +289,14 @@ class NodeMutation
259
289
  # if strategy is set to KEEP_RUNNING.
260
290
  # @return {NodeMutation::Result}
261
291
  def test
292
+ @actions = flatten_actions(@actions)
262
293
  if @actions.length == 0
263
294
  return NodeMutation::Result.new(affected: false, conflicted: false)
264
295
  end
265
296
 
266
297
  @transform_proc.call(@actions) if @transform_proc
267
- @actions.sort_by! { |action| [action.start, action.end] }
268
- conflict_actions = get_conflict_actions
298
+ sort_actions!(@actions)
299
+ conflict_actions = get_conflict_actions(@actions)
269
300
  if conflict_actions.size > 0 && strategy?(Strategy::THROW_ERROR)
270
301
  raise ConflictActionError, "mutation actions are conflicted"
271
302
  end
@@ -277,27 +308,85 @@ class NodeMutation
277
308
 
278
309
  private
279
310
 
311
+ # It flattens a series of actions by removing any CombinedAction
312
+ # objects that contain only a single action. This is done recursively.
313
+ def flatten_actions(actions)
314
+ new_actions = []
315
+ actions.each do |action|
316
+ if action.is_a?(CombinedAction)
317
+ new_actions << flatten_combined_action(action)
318
+ else
319
+ new_actions << action
320
+ end
321
+ end
322
+ new_actions.compact
323
+ end
324
+
325
+ # It flattens a combined action.
326
+ def flatten_combined_action(action)
327
+ if action.actions.empty?
328
+ nil
329
+ elsif action.actions.size == 1
330
+ if action.actions.first.is_a?(CombinedAction)
331
+ flatten_combined_action(action.actions.first)
332
+ else
333
+ action.actions.first
334
+ end
335
+ else
336
+ action.actions = flatten_actions(action.actions)
337
+ action
338
+ end
339
+ end
340
+
341
+ # Sort actions by start position and end position.
342
+ # @param actions [Array<NodeMutation::Action>]
343
+ # @return [Array<NodeMutation::Action>] sorted actions
344
+ def sort_actions!(actions)
345
+ actions.sort_by! { |action| [action.start, action.end] }
346
+ actions.each do |action|
347
+ sort_actions!(action.actions) if action.is_a?(CombinedAction)
348
+ end
349
+ end
350
+
351
+ # Rewrite source code with actions.
352
+ # @param source [String] source code
353
+ # @param actions [Array<NodeMutation::Action>] actions
354
+ # @return [String] new source code
355
+ def rewrite_source(source, actions)
356
+ actions.reverse_each do |action|
357
+ if action.is_a?(CombinedAction)
358
+ source = rewrite_source(source, action.actions)
359
+ else
360
+ source[action.start...action.end] = action.new_code if action.new_code
361
+ end
362
+ end
363
+ source
364
+ end
365
+
280
366
  # It changes source code from bottom to top, and it can change source code twice at the same time,
281
367
  # So if there is an overlap between two actions, it removes the conflict actions and operate them in the next loop.
282
- def get_conflict_actions
283
- i = @actions.length - 1
368
+ def get_conflict_actions(actions)
369
+ i = actions.length - 1
284
370
  j = i - 1
285
371
  conflict_actions = []
286
372
  return [] if i < 0
287
373
 
288
- begin_pos = @actions[i].start
289
- end_pos = @actions[i].end
374
+ begin_pos = actions[i].start
375
+ end_pos = actions[i].end
290
376
  while j > -1
291
377
  # if we have two actions with overlapped range.
292
- if begin_pos < @actions[j].end
293
- conflict_actions << @actions.delete_at(j)
378
+ if begin_pos < actions[j].end
379
+ conflict_actions << actions.delete_at(j)
294
380
  else
295
381
  i = j
296
- begin_pos = @actions[i].start
297
- end_pos = @actions[i].end
382
+ begin_pos = actions[i].start
383
+ end_pos = actions[i].end
298
384
  end
299
385
  j -= 1
300
386
  end
387
+ actions.each do |action|
388
+ conflict_actions.concat(get_conflict_actions(action.actions)) if action.is_a?(CombinedAction)
389
+ end
301
390
  conflict_actions
302
391
  end
303
392
 
@@ -0,0 +1,3 @@
1
+ class NodeMutation::Helper
2
+ def self.iterate_actions: (actions: Array[NodeMutation::Action]) { (action: NodeMutation::Action) -> void } -> void
3
+ end
@@ -1,4 +1,4 @@
1
- module NodeMutation[T]
1
+ class NodeMutation[T]
2
2
  VERSION: String
3
3
 
4
4
  class MethodNotSupported < StandardError
@@ -39,6 +39,8 @@ module NodeMutation[T]
39
39
 
40
40
  def noop: (node: T) -> void
41
41
 
42
+ def combine: () { () -> void } -> void
43
+
42
44
  def process: () -> NodeMutation::Result
43
45
 
44
46
  def test: () -> NodeMutation::Result
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: node_mutation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.19.4
4
+ version: 1.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-17 00:00:00.000000000 Z
11
+ date: 2023-09-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: ast node mutation apis
14
14
  email:
@@ -28,6 +28,7 @@ files:
28
28
  - lib/node_mutation.rb
29
29
  - lib/node_mutation/action.rb
30
30
  - lib/node_mutation/action/append_action.rb
31
+ - lib/node_mutation/action/combined_action.rb
31
32
  - lib/node_mutation/action/delete_action.rb
32
33
  - lib/node_mutation/action/indent_action.rb
33
34
  - lib/node_mutation/action/insert_action.rb
@@ -39,6 +40,7 @@ files:
39
40
  - lib/node_mutation/adapter.rb
40
41
  - lib/node_mutation/adapter/parser.rb
41
42
  - lib/node_mutation/adapter/syntax_tree.rb
43
+ - lib/node_mutation/helper.rb
42
44
  - lib/node_mutation/result.rb
43
45
  - lib/node_mutation/strategy.rb
44
46
  - lib/node_mutation/struct.rb
@@ -46,6 +48,7 @@ files:
46
48
  - node_mutation.gemspec
47
49
  - sig/node_mutation.rbs
48
50
  - sig/node_mutation/adapter.rbs
51
+ - sig/node_mutation/helper.rbs
49
52
  - sig/node_mutation/result.rbs
50
53
  - sig/node_mutation/strategy.rbs
51
54
  - sig/node_mutation/struct.rbs