node_mutation 1.19.4 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
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