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 +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/lib/node_mutation/action/combined_action.rb +29 -0
- data/lib/node_mutation/helper.rb +14 -0
- data/lib/node_mutation/version.rb +1 -1
- data/lib/node_mutation.rb +110 -21
- data/sig/node_mutation/helper.rbs +3 -0
- data/sig/node_mutation.rbs +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd49bceca35429738e6137151b99634e0ef83801214ba58e80c5e1d67b299b07
|
4
|
+
data.tar.gz: ef1f95f7edcf89b73b3eeb11137e374c813460cd4c5c8c67b970a5b29acc4e47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -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
|
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
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
216
|
-
|
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
|
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
|
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 =
|
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
|
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 =
|
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 =
|
289
|
-
end_pos =
|
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 <
|
293
|
-
conflict_actions <<
|
378
|
+
if begin_pos < actions[j].end
|
379
|
+
conflict_actions << actions.delete_at(j)
|
294
380
|
else
|
295
381
|
i = j
|
296
|
-
begin_pos =
|
297
|
-
end_pos =
|
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
|
|
data/sig/node_mutation.rbs
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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.
|
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-
|
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
|