node_mutation 1.22.3 → 1.23.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: 235e331a54f9181d9986ccb6ee62ef172b399a323b697b8090c954e685a117f0
4
- data.tar.gz: 11e838f63be7a940176ad6880300aeebde419e3bb923016039a821496648b9ca
3
+ metadata.gz: 667309a875501329f61441ddc89143500b02d87e83be0a6aa324e78438cfd397
4
+ data.tar.gz: 565be945f6bbfd0976dafb86bb346be696665bd32a72227cf9a4a2d16e75fa45
5
5
  SHA512:
6
- metadata.gz: 0cd144acfbcc3db98c407332f3408bde088ad14156bd5a9c07bc1d1042872339cc71b25b5896c76ff720d8efe80add83789f6889125e55dd7bcb39cc0cf43512
7
- data.tar.gz: cdd66a6fa04ba9ad54d788e2f5680045b497a94b48875d4a98efb3a57dfc5a864f9f666a12ab687c16f5a2a59013b39b7d5ec8ead6264e63e22b6faf1edf527a
6
+ metadata.gz: ed08dbed41c4aa8dce675995a19736ce84877fc0334ab448c04019c7e413204ee2e0c6674ea2d779048962bccbf970990d0b72f89fb7bc17845cb0034b6400ce
7
+ data.tar.gz: 8488422fbcabd15c24c607c1a2b8229bf3a7be810a2d34f0568ceb79a82cc87ebdd06625ab0d76cf68099fd894a067267fc0dde257f5999aa48a8f0ef1c40346
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # NodeMutation
2
2
 
3
+ ## 1.23.0 (2024-02-11)
4
+
5
+ * Support `prism`
6
+
7
+ ## 1.22.4 (2024-01-30)
8
+
9
+ * Revert "add action methods to GroupAction"
10
+
3
11
  ## 1.22.3 (2024-01-30)
4
12
 
5
13
  * `child_node_range` supports `arg` `name`
data/Gemfile CHANGED
@@ -9,8 +9,8 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
- gem "parser"
13
12
  gem "parser_node_ext"
14
13
  gem "syntax_tree_ext"
14
+ gem "prism_ext"
15
15
  gem "guard"
16
16
  gem "guard-rspec"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.22.3)
4
+ node_mutation (1.23.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -34,16 +34,19 @@ GEM
34
34
  notiffany (0.1.3)
35
35
  nenv (~> 0.1)
36
36
  shellany (~> 0.0)
37
- parser (3.2.2.3)
37
+ parser (3.3.0.5)
38
38
  ast (~> 2.4.1)
39
39
  racc
40
- parser_node_ext (1.2.1)
40
+ parser_node_ext (1.2.2)
41
41
  parser
42
42
  prettier_print (1.2.1)
43
+ prism (0.22.0)
44
+ prism_ext (0.2.1)
45
+ prism
43
46
  pry (0.14.1)
44
47
  coderay (~> 1.1)
45
48
  method_source (~> 1.0)
46
- racc (1.7.1)
49
+ racc (1.7.3)
47
50
  rake (13.0.6)
48
51
  rb-fsevent (0.11.1)
49
52
  rb-inotify (0.10.1)
@@ -62,7 +65,7 @@ GEM
62
65
  rspec-support (~> 3.11.0)
63
66
  rspec-support (3.11.0)
64
67
  shellany (0.0.1)
65
- syntax_tree (6.1.1)
68
+ syntax_tree (6.2.0)
66
69
  prettier_print (>= 1.2.0)
67
70
  syntax_tree_ext (0.6.3)
68
71
  syntax_tree
@@ -78,8 +81,8 @@ DEPENDENCIES
78
81
  guard
79
82
  guard-rspec
80
83
  node_mutation!
81
- parser
82
84
  parser_node_ext
85
+ prism_ext
83
86
  rake (~> 13.0)
84
87
  rspec (~> 3.0)
85
88
  syntax_tree_ext
@@ -2,27 +2,18 @@
2
2
 
3
3
  # GroupAction is compose of multiple actions.
4
4
  class NodeMutation::GroupAction < NodeMutation::Action
5
- include NodeMutation::Actionable
6
-
7
5
  DEFAULT_START = 2**30
8
6
 
9
7
  # Initialize a GroupAction.
10
- def initialize(adapter:, &block)
8
+ def initialize
11
9
  @actions = []
12
10
  @type = :group
13
- @adapter = adapter
14
- @block = block
15
11
  end
16
12
 
17
13
  def new_code
18
14
  nil
19
15
  end
20
16
 
21
- def process
22
- instance_eval(&@block)
23
- super
24
- end
25
-
26
17
  private
27
18
 
28
19
  # Calculate the begin and end positions.
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+ require 'prism_ext'
5
+
6
+ class NodeMutation::PrismAdapter < NodeMutation::Adapter
7
+ def get_source(node)
8
+ if node.is_a?(Array)
9
+ return node.first.source[node.first.location.start_offset...node.last.location.end_offset]
10
+ end
11
+
12
+ node.source[node.location.start_offset...node.location.end_offset]
13
+ end
14
+
15
+ # It gets the new source code after evaluating the node.
16
+ # @param node [Prism::Node] The node to evaluate.
17
+ # @param code [String] The code to evaluate.
18
+ # @return [String] The new source code.
19
+ # @example
20
+ # node = Prism.parse('class Synvert; end').value.statements.body.first
21
+ # rewritten_source(node, '{{constant}}') # 'Synvert'
22
+ #
23
+ # # index for node array
24
+ # node = Prism.parse("foo.bar(a, b)").value.statements.body.first
25
+ # rewritten_source(node, '{{arguments.arguments.parts.-1}}')) # 'b'
26
+ #
27
+ # # {key}_assoc for HashNode node
28
+ # node = Prism.parse("after_commit :do_index, on: :create, if: :indexable?").value.statements.body.first
29
+ # rewritten_source(node, '{{arguments.parts.-1.on_assoc}}')) # 'on: :create'
30
+ #
31
+ # # {key}_value for hash node
32
+ # node = Prism.parse("after_commit :do_index, on: :create, if: :indexable?").value.statements.body.first
33
+ # rewritten_source(node, '{{arguments.parts.-1.on_value}}')) # ':create'
34
+ #
35
+ # # to_single_quote for StringNode
36
+ # node = Prism.parse('"foo"').value.statements.body.first
37
+ # rewritten_source(node, 'to_single_quote') # "'foo'"
38
+ #
39
+ # # to_double_quote for StringNode
40
+ # node = Prism.parse("'foo'").value.statements.body.first
41
+ # rewritten_source(node, 'to_double_quote') # '"foo"'
42
+ #
43
+ # # to_symbol for StringNode
44
+ # node = Prism.parse("'foo'").value.statements.body.first
45
+ # rewritten_source(node, 'to_symbol') # ':foo'
46
+ #
47
+ # # to_string for SymbolNode
48
+ # node = Prism.parse(":foo").value.statements.body.first
49
+ # rewritten_source(node, 'to_string') # 'foo'
50
+ #
51
+ # # to_lambda_literal for CallNode with lambda
52
+ # node = Prism.parse('lambda { foobar }').value.statements.body.first
53
+ # rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'
54
+ #
55
+ # # strip_curly_braces for HashNode
56
+ # node = Prism.parse("{ foo: 'bar' }").value.statements.body.first
57
+ # rewritten_source(node, 'strip_curly_braces') # "foo: 'bar'"
58
+ #
59
+ # # wrap_curly_braces for KeywordHashNode
60
+ # node = Prism.parse("test(foo: 'bar')").value.statements.body.first
61
+ # rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') # "{ foo: 'bar' }"
62
+ def rewritten_source(node, code)
63
+ code.gsub(/{{(.+?)}}/m) do
64
+ old_code = Regexp.last_match(1)
65
+ evaluated = child_node_by_name(node, old_code)
66
+ case evaluated
67
+ when Prism::Node
68
+ get_source(evaluated)
69
+ when Array
70
+ if evaluated.size > 0
71
+ source = get_source(evaluated)
72
+ lines = source.split "\n"
73
+ lines_count = lines.length
74
+ if lines_count > 1 && lines_count == evaluated.size
75
+ new_code = []
76
+ lines.each_with_index { |line, index|
77
+ new_code << (index == 0 ? line : line[get_indent(evaluated.first) - NodeMutation.tab_width..-1])
78
+ }
79
+ new_code.join("\n")
80
+ else
81
+ source
82
+ end
83
+ end
84
+ when String, Symbol, Integer, Float
85
+ evaluated
86
+ when NilClass
87
+ ''
88
+ else
89
+ raise "can not parse \"#{code}\""
90
+ end
91
+ end
92
+ end
93
+
94
+ def file_source(node)
95
+ node.source
96
+ end
97
+
98
+ # Get the range of the child node.
99
+ # @param node [Parser::AST::Node] The node.
100
+ # @param child_name [String] THe name to find child node.
101
+ # @return {NodeMutation::Struct::Range} The range of the child node.
102
+ # @example
103
+ # node = Prism.parse('foo.bar(test)').value.statements.body.first
104
+ # child_node_range(node, 'receiver') # { start: 0, end: 'foo'.length }
105
+ #
106
+ # # node array
107
+ # node = Prism.parse('foo.bar(a, b)').value.statements.body.first
108
+ # child_node_range(node, 'arguments.arguments') # { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }
109
+ #
110
+ # # index for node array
111
+ # node = Prism.parse('foo.bar(a, b)').value.statements.body.first
112
+ # child_node_range(node, 'arguments.arguments.parts.-1') # { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }
113
+ #
114
+ # # operator of Binary node
115
+ # node = Prism.parse('foo | bar').value.statements.body.first
116
+ # child_node_range(node, 'operator') # { start: 'foo '.length, end: 'foo |'.length }
117
+ def child_node_range(node, child_name)
118
+ direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
119
+
120
+ if node.is_a?(Array)
121
+ if direct_child_name =~ INDEX_REGEXP
122
+ child_node = node[direct_child_name.to_i]
123
+ raise NodeMutation::MethodNotSupported,
124
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless child_node
125
+ return child_node_range(child_node, nested_child_name) if nested_child_name
126
+
127
+ return NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
128
+ end
129
+
130
+ raise NodeMutation::MethodNotSupported,
131
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
132
+
133
+ child_node = node.send(direct_child_name)
134
+ return child_node_range(child_node, nested_child_name) if nested_child_name
135
+
136
+ return NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
137
+ end
138
+
139
+ if node.respond_to?("#{child_name}_loc")
140
+ node_loc = node.send("#{child_name}_loc")
141
+ if node_loc
142
+ NodeMutation::Struct::Range.new(node_loc.start_offset, node_loc.end_offset)
143
+ end
144
+ else
145
+ raise NodeMutation::MethodNotSupported,
146
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
147
+
148
+ child_node = node.send(direct_child_name)
149
+
150
+ return child_node_range(child_node, nested_child_name) if nested_child_name
151
+
152
+ return nil if child_node.nil?
153
+
154
+ if child_node.is_a?(Prism::Node)
155
+ return(
156
+ NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
157
+ )
158
+ end
159
+
160
+ return(
161
+ NodeMutation::Struct::Range.new(child_node.first.location.start_offset, child_node.last.location.end_offset)
162
+ )
163
+ end
164
+ end
165
+
166
+ def get_start(node, child_name = nil)
167
+ node = child_node_by_name(node, child_name) if child_name
168
+ node.location.start_offset
169
+ end
170
+
171
+ def get_end(node, child_name = nil)
172
+ node = child_node_by_name(node, child_name) if child_name
173
+ node.location.end_offset
174
+ end
175
+
176
+ def get_start_loc(node, child_name = nil)
177
+ node = child_node_by_name(node, child_name) if child_name
178
+ NodeMutation::Struct::Location.new(node.location.start_line, node.location.start_column)
179
+ end
180
+
181
+ def get_end_loc(node, child_name = nil)
182
+ node = child_node_by_name(node, child_name) if child_name
183
+ NodeMutation::Struct::Location.new(node.location.end_line, node.location.end_column)
184
+ end
185
+
186
+ def get_indent(node)
187
+ node.location.start_column
188
+ end
189
+
190
+ private
191
+
192
+ def child_node_by_name(node, child_name)
193
+ direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
194
+
195
+ if node.is_a?(Array)
196
+ if direct_child_name =~ INDEX_REGEXP
197
+ child_node = node[direct_child_name.to_i]
198
+ raise NodeMutation::MethodNotSupported,
199
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless child_node
200
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
201
+
202
+ return child_node
203
+ end
204
+
205
+ raise NodeMutation::MethodNotSupported,
206
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
207
+
208
+ child_node = node.send(direct_child_name)
209
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
210
+
211
+ return child_node
212
+ end
213
+
214
+ if node.respond_to?(direct_child_name)
215
+ child_node = node.send(direct_child_name)
216
+ elsif direct_child_name == 'to_symbol' && node.is_a?(Prism::StringNode)
217
+ child_node = ":#{node.to_value}"
218
+ elsif direct_child_name == 'to_string' && node.is_a?(Prism::SymbolNode)
219
+ child_node = node.to_value.to_s
220
+ elsif direct_child_name == 'to_single_quote' && node.is_a?(Prism::StringNode)
221
+ child_node = "'#{node.to_value}'"
222
+ elsif direct_child_name == 'to_double_quote' && node.is_a?(Prism::StringNode)
223
+ child_node = "\"#{node.to_value}\""
224
+ elsif direct_child_name == 'to_lambda_literal' && node.is_a?(Prism::CallNode) && node.name == :lambda
225
+ if node.block.parameters
226
+ child_node = "->(#{node.block.parameters.parameters.to_source}) { #{node.block.body.to_source} }"
227
+ else
228
+ child_node = "-> #{node.block.to_source}"
229
+ end
230
+ elsif direct_child_name == 'strip_curly_braces' && node.is_a?(Prism::HashNode)
231
+ child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
232
+ elsif direct_child_name == 'wrap_curly_braces' && node.is_a?(Prism::KeywordHashNode)
233
+ child_node = "{ #{node.to_source} }"
234
+ else
235
+ raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
236
+ end
237
+
238
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
239
+
240
+ child_node
241
+ end
242
+ end
@@ -17,47 +17,47 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
17
17
  # @param code [String] The code to evaluate.
18
18
  # @return [String] The new source code.
19
19
  # @example
20
- # node = SyntaxTree::Parser.new('class Synvert; end').parse.statements.body.first
20
+ # node = SyntaxTree.parse('class Synvert; end').statements.body.first
21
21
  # rewritten_source(node, '{{constant}}') # 'Synvert'
22
22
  #
23
23
  # # index for node array
24
- # node = SyntaxTree::Parser.new("foo.bar(a, b)").parse.statements.body.first
24
+ # node = SyntaxTree.parse("foo.bar(a, b)").statements.body.first
25
25
  # rewritten_source(node, '{{arguments.arguments.parts.-1}}')) # 'b'
26
26
  #
27
27
  # # {key}_assoc for HashLiteral node
28
- # node = SyntaxTree::Parser.new("after_commit :do_index, on: :create, if: :indexable?").parse.statements.body.first
28
+ # node = SyntaxTree.parse("after_commit :do_index, on: :create, if: :indexable?").statements.body.first
29
29
  # rewritten_source(node, '{{arguments.parts.-1.on_assoc}}')) # 'on: :create'
30
30
  #
31
31
  # # {key}_value for hash node
32
- # node = SyntaxTree::Parser.new("after_commit :do_index, on: :create, if: :indexable?").parse.statements.body.first
32
+ # node = SyntaxTree.parse("after_commit :do_index, on: :create, if: :indexable?").statements.body.first
33
33
  # rewritten_source(node, '{{arguments.parts.-1.on_value}}')) # ':create'
34
34
  #
35
35
  # # to_single_quote for StringLiteral node
36
- # node = SyntaxTree::Parser.new('"foo"').parse.statements.body.first
36
+ # node = SyntaxTree.parse('"foo"').statements.body.first
37
37
  # rewritten_source(node, 'to_single_quote') # "'foo'"
38
38
  #
39
39
  # # to_double_quote for StringLiteral node
40
- # node = SyntaxTree::Parser.new("'foo'").parse.statements.body.first
40
+ # node = SyntaxTree.parse("'foo'").statements.body.first
41
41
  # rewritten_source(node, 'to_double_quote') # '"foo"'
42
42
  #
43
43
  # # to_symbol for StringLiteral node
44
- # node = SyntaxTree::Parser.new("'foo'").parse.statements.body.first
44
+ # node = SyntaxTree.parse("'foo'").statements.body.first
45
45
  # rewritten_source(node, 'to_symbol') # ':foo'
46
46
  #
47
47
  # # to_string for SymbolLiteral node
48
- # node = SyntaxTree::Parser.new(":foo").parse.statements.body.first
48
+ # node = SyntaxTree.parse(":foo").statements.body.first
49
49
  # rewritten_source(node, 'to_string') # 'foo'
50
50
  #
51
51
  # # to_lambda_literal for MethodAddBlock node
52
- # node = SyntaxTree::Parser.new('lambda { foobar }').parse.statements.body.first
52
+ # node = SyntaxTree.parse('lambda { foobar }').statements.body.first
53
53
  # rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'
54
54
  #
55
55
  # # strip_curly_braces for HashLiteral node
56
- # node = SyntaxTree::Parser.new("{ foo: 'bar' }").parse.statements.body.first
56
+ # node = SyntaxTree.parse("{ foo: 'bar' }").statements.body.first
57
57
  # rewritten_source(node, 'strip_curly_braces') # "foo: 'bar'"
58
58
  #
59
59
  # # wrap_curly_braces for BareAssocHash node
60
- # node = SyntaxTree::Parser.new("test(foo: 'bar')").parse.statements.body.first
60
+ # node = SyntaxTree.parse("test(foo: 'bar')").statements.body.first
61
61
  # rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') # "{ foo: 'bar' }"
62
62
  def rewritten_source(node, code)
63
63
  code.gsub(/{{(.+?)}}/m) do
@@ -100,19 +100,19 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
100
100
  # @param child_name [String] THe name to find child node.
101
101
  # @return {NodeMutation::Struct::Range} The range of the child node.
102
102
  # @example
103
- # node = SyntaxTree::Parser.new('foo.bar(test)').parse.statements.body.first
103
+ # node = SyntaxTree.parse('foo.bar(test)').statements.body.first
104
104
  # child_node_range(node, 'receiver') # { start: 0, end: 'foo'.length }
105
105
  #
106
106
  # # node array
107
- # node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
107
+ # node = SyntaxTree.parse('foo.bar(a, b)').statements.body.first
108
108
  # child_node_range(node, 'arguments.arguments') # { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }
109
109
  #
110
110
  # # index for node array
111
- # node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
111
+ # node = SyntaxTree.parse('foo.bar(a, b)').statements.body.first
112
112
  # child_node_range(node, 'arguments.arguments.parts.-1') # { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }
113
113
  #
114
114
  # # operator of Binary node
115
- # node = SyntaxTree::Parser.new('foo | bar').parse.statements.body.first
115
+ # node = SyntaxTree.parse('foo | bar').statements.body.first
116
116
  # child_node_range(node, 'operator') # { start: 'foo '.length, end: 'foo |'.length }
117
117
  def child_node_range(node, child_name)
118
118
  direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeMutation
4
- VERSION = "1.22.3"
4
+ VERSION = "1.23.0"
5
5
  end
data/lib/node_mutation.rb CHANGED
@@ -7,9 +7,9 @@ class NodeMutation
7
7
  class ConflictActionError < StandardError; end
8
8
  class InvalidAdapterError < StandardError; end
9
9
 
10
- autoload :Actionable, "node_mutation/actionable"
11
10
  autoload :Adapter, "node_mutation/adapter"
12
11
  autoload :ParserAdapter, "node_mutation/adapter/parser"
12
+ autoload :PrismAdapter, "node_mutation/adapter/prism"
13
13
  autoload :SyntaxTreeAdapter, "node_mutation/adapter/syntax_tree"
14
14
  autoload :Action, 'node_mutation/action'
15
15
  autoload :AppendAction, 'node_mutation/action/append_action'
@@ -27,8 +27,6 @@ class NodeMutation
27
27
  autoload :Struct, 'node_mutation/struct'
28
28
  autoload :Helper, 'node_mutation/helper'
29
29
 
30
- include Actionable
31
-
32
30
  # @!attribute [r] actions
33
31
  # @return [Array<NodeMutation::Struct::Action>]
34
32
  attr_reader :actions, :adapter
@@ -74,6 +72,182 @@ class NodeMutation
74
72
  @adapter = get_adapter_instance(adapter)
75
73
  end
76
74
 
75
+ # Append code to the ast node.
76
+ # @param node [Node] ast node
77
+ # @param code [String] new code to append
78
+ # @example
79
+ # source code of the ast node is
80
+ # def teardown
81
+ # clean_something
82
+ # end
83
+ # then we call
84
+ # mutation.append(node, 'super')
85
+ # the source code will be rewritten to
86
+ # def teardown
87
+ # clean_something
88
+ # super
89
+ # end
90
+ def append(node, code)
91
+ @actions << AppendAction.new(node, code, adapter: @adapter).process
92
+ end
93
+
94
+ # Delete source code of the child ast node.
95
+ # @param node [Node] ast node
96
+ # @param selectors [Array<Symbol>] selector names of child node.
97
+ # @param and_comma [Boolean] delete extra comma.
98
+ # @example
99
+ # source code of the ast node is
100
+ # FactoryBot.create(...)
101
+ # then we call
102
+ # mutation.delete(node, :receiver, :dot)
103
+ # the source code will be rewritten to
104
+ # create(...)
105
+ def delete(node, *selectors, and_comma: false)
106
+ @actions << DeleteAction.new(node, *selectors, and_comma: and_comma, adapter: @adapter).process
107
+ end
108
+
109
+ # Insert code to the ast node.
110
+ # @param node [Node] ast node
111
+ # @param code [String] code need to be inserted.
112
+ # @param at [String] insert position, beginning or end
113
+ # @param to [String] where to insert, if it is nil, will insert to current node.
114
+ # @param and_comma [Boolean] insert extra comma.
115
+ # @example
116
+ # source code of the ast node is
117
+ # open('http://test.com')
118
+ # then we call
119
+ # mutation.insert(node, 'URI.', at: 'beginning')
120
+ # the source code will be rewritten to
121
+ # URI.open('http://test.com')
122
+ def insert(node, code, at: 'end', to: nil, and_comma: false)
123
+ @actions << InsertAction.new(node, code, at: at, to: to, and_comma: and_comma, adapter: @adapter).process
124
+ end
125
+
126
+ # Prepend code to the ast node.
127
+ # @param node [Node] ast node
128
+ # @param code [String] new code to prepend.
129
+ # @example
130
+ # source code of the ast node is
131
+ # def setup
132
+ # do_something
133
+ # end
134
+ # then we call
135
+ # mutation.prepend(node, 'super')
136
+ # the source code will be rewritten to
137
+ # def setup
138
+ # super
139
+ # do_something
140
+ # end
141
+ def prepend(node, code)
142
+ @actions << PrependAction.new(node, code, adapter: @adapter).process
143
+ end
144
+
145
+ # Remove source code of the ast node.
146
+ # @param node [Node] ast node
147
+ # @param and_comma [Boolean] delete extra comma.
148
+ # @example
149
+ # source code of the ast node is
150
+ # puts "test"
151
+ # then we call
152
+ # mutation.remove(node)
153
+ # the source code will be removed
154
+ def remove(node, and_comma: false)
155
+ @actions << RemoveAction.new(node, and_comma: and_comma, adapter: @adapter).process
156
+ end
157
+
158
+ # Replace child node of the ast node with new code.
159
+ # @param node [Node] ast node
160
+ # @param selectors [Array<Symbol>] selector names of child node.
161
+ # @param with [String] code need to be replaced with.
162
+ # @example
163
+ # source code of the ast node is
164
+ # assert(object.empty?)
165
+ # then we call
166
+ # mutation.replace(node, :message, with: 'assert_empty')
167
+ # mutation.replace(node, :arguments, with: '{{arguments.first.receiver}}')
168
+ # the source code will be rewritten to
169
+ # assert_empty(object)
170
+ def replace(node, *selectors, with:)
171
+ @actions << ReplaceAction.new(node, *selectors, with: with, adapter: @adapter).process
172
+ end
173
+
174
+ # Replace source code of the ast node with new code.
175
+ # @param node [Node] ast node
176
+ # @param code [String] code need to be replaced with.
177
+ # @example
178
+ # source code of the ast node is
179
+ # obj.stub(:foo => 1, :bar => 2)
180
+ # then we call
181
+ # replace_with 'allow({{receiver}}).to receive_messages({{arguments}})'
182
+ # the source code will be rewritten to
183
+ # allow(obj).to receive_messages(:foo => 1, :bar => 2)
184
+ def replace_with(node, code)
185
+ @actions << ReplaceWithAction.new(node, code, adapter: @adapter).process
186
+ end
187
+
188
+ # Wrap source code of the ast node with prefix and suffix code.
189
+ # @param node [Node] ast node
190
+ # @param prefix [String] prefix code need to be wrapped with.
191
+ # @param suffix [String] suffix code need to be wrapped with.
192
+ # @param newline [Boolean] add newline after prefix and before suffix.
193
+ # @example
194
+ # source code of the ast node is
195
+ # class Foobar
196
+ # end
197
+ # then we call
198
+ # wrap(node, prefix: 'module Synvert', suffix: 'end', newline: true)
199
+ # the source code will be rewritten to
200
+ # module Synvert
201
+ # class Foobar
202
+ # end
203
+ # end
204
+ def wrap(node, prefix:, suffix:, newline: false)
205
+ if newline
206
+ indentation = @adapter.get_start_loc(node).column
207
+ group do
208
+ insert node, prefix + "\n" + (' ' * indentation), at: 'beginning'
209
+ insert node, "\n" + (' ' * indentation) + suffix, at: 'end'
210
+ indent node
211
+ end
212
+ else
213
+ group do
214
+ insert node, prefix, at: 'beginning'
215
+ insert node, suffix, at: 'end'
216
+ end
217
+ end
218
+ end
219
+
220
+ # Indent source code of the ast node
221
+ # @param node [Node] ast node
222
+ # @example
223
+ # source code of ast node is
224
+ # class Foobar
225
+ # end
226
+ # then we call
227
+ # indent(node)
228
+ # the source code will be rewritten to
229
+ # class Foobar
230
+ # end
231
+ def indent(node)
232
+ @actions << IndentAction.new(node, adapter: @adapter).process
233
+ end
234
+
235
+ # No operation.
236
+ # @param node [Node] ast node
237
+ def noop(node)
238
+ @actions << NoopAction.new(node, adapter: @adapter).process
239
+ end
240
+
241
+ # group multiple actions
242
+ def group
243
+ current_actions = @actions
244
+ group_action = GroupAction.new
245
+ @actions = group_action.actions
246
+ yield
247
+ @actions = current_actions
248
+ @actions << group_action.process
249
+ end
250
+
77
251
  # Process actions and return the new source.
78
252
  #
79
253
  # If there's an action range conflict,
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.22.3
4
+ version: 1.23.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: 2024-01-30 00:00:00.000000000 Z
11
+ date: 2024-02-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: ast node mutation apis
14
14
  email:
@@ -37,9 +37,9 @@ files:
37
37
  - lib/node_mutation/action/remove_action.rb
38
38
  - lib/node_mutation/action/replace_action.rb
39
39
  - lib/node_mutation/action/replace_with_action.rb
40
- - lib/node_mutation/actionable.rb
41
40
  - lib/node_mutation/adapter.rb
42
41
  - lib/node_mutation/adapter/parser.rb
42
+ - lib/node_mutation/adapter/prism.rb
43
43
  - lib/node_mutation/adapter/syntax_tree.rb
44
44
  - lib/node_mutation/helper.rb
45
45
  - lib/node_mutation/result.rb
@@ -1,181 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module NodeMutation::Actionable
4
- # Append code to the ast node.
5
- # @param node [Node] ast node
6
- # @param code [String] new code to append
7
- # @example
8
- # source code of the ast node is
9
- # def teardown
10
- # clean_something
11
- # end
12
- # then we call
13
- # mutation.append(node, 'super')
14
- # the source code will be rewritten to
15
- # def teardown
16
- # clean_something
17
- # super
18
- # end
19
- def append(node, code)
20
- @actions << NodeMutation::AppendAction.new(node, code, adapter: @adapter).process
21
- end
22
-
23
- # Delete source code of the child ast node.
24
- # @param node [Node] ast node
25
- # @param selectors [Array<Symbol>] selector names of child node.
26
- # @param and_comma [Boolean] delete extra comma.
27
- # @example
28
- # source code of the ast node is
29
- # FactoryBot.create(...)
30
- # then we call
31
- # mutation.delete(node, :receiver, :dot)
32
- # the source code will be rewritten to
33
- # create(...)
34
- def delete(node, *selectors, and_comma: false)
35
- @actions << NodeMutation::DeleteAction.new(node, *selectors, and_comma: and_comma, adapter: @adapter).process
36
- end
37
-
38
- # Insert code to the ast node.
39
- # @param node [Node] ast node
40
- # @param code [String] code need to be inserted.
41
- # @param at [String] insert position, beginning or end
42
- # @param to [String] where to insert, if it is nil, will insert to current node.
43
- # @param and_comma [Boolean] insert extra comma.
44
- # @example
45
- # source code of the ast node is
46
- # open('http://test.com')
47
- # then we call
48
- # mutation.insert(node, 'URI.', at: 'beginning')
49
- # the source code will be rewritten to
50
- # URI.open('http://test.com')
51
- def insert(node, code, at: 'end', to: nil, and_comma: false)
52
- @actions << NodeMutation::InsertAction.new(
53
- node,
54
- code,
55
- at: at,
56
- to: to,
57
- and_comma: and_comma,
58
- adapter: @adapter
59
- ).process
60
- end
61
-
62
- # Prepend code to the ast node.
63
- # @param node [Node] ast node
64
- # @param code [String] new code to prepend.
65
- # @example
66
- # source code of the ast node is
67
- # def setup
68
- # do_something
69
- # end
70
- # then we call
71
- # mutation.prepend(node, 'super')
72
- # the source code will be rewritten to
73
- # def setup
74
- # super
75
- # do_something
76
- # end
77
- def prepend(node, code)
78
- @actions << NodeMutation::PrependAction.new(node, code, adapter: @adapter).process
79
- end
80
-
81
- # Remove source code of the ast node.
82
- # @param node [Node] ast node
83
- # @param and_comma [Boolean] delete extra comma.
84
- # @example
85
- # source code of the ast node is
86
- # puts "test"
87
- # then we call
88
- # mutation.remove(node)
89
- # the source code will be removed
90
- def remove(node, and_comma: false)
91
- @actions << NodeMutation::RemoveAction.new(node, and_comma: and_comma, adapter: @adapter).process
92
- end
93
-
94
- # Replace child node of the ast node with new code.
95
- # @param node [Node] ast node
96
- # @param selectors [Array<Symbol>] selector names of child node.
97
- # @param with [String] code need to be replaced with.
98
- # @example
99
- # source code of the ast node is
100
- # assert(object.empty?)
101
- # then we call
102
- # mutation.replace(node, :message, with: 'assert_empty')
103
- # mutation.replace(node, :arguments, with: '{{arguments.first.receiver}}')
104
- # the source code will be rewritten to
105
- # assert_empty(object)
106
- def replace(node, *selectors, with:)
107
- @actions << NodeMutation::ReplaceAction.new(node, *selectors, with: with, adapter: @adapter).process
108
- end
109
-
110
- # Replace source code of the ast node with new code.
111
- # @param node [Node] ast node
112
- # @param code [String] code need to be replaced with.
113
- # @example
114
- # source code of the ast node is
115
- # obj.stub(:foo => 1, :bar => 2)
116
- # then we call
117
- # replace_with 'allow({{receiver}}).to receive_messages({{arguments}})'
118
- # the source code will be rewritten to
119
- # allow(obj).to receive_messages(:foo => 1, :bar => 2)
120
- def replace_with(node, code)
121
- @actions << NodeMutation::ReplaceWithAction.new(node, code, adapter: @adapter).process
122
- end
123
-
124
- # Wrap source code of the ast node with prefix and suffix code.
125
- # @param node [Node] ast node
126
- # @param prefix [String] prefix code need to be wrapped with.
127
- # @param suffix [String] suffix code need to be wrapped with.
128
- # @param newline [Boolean] add newline after prefix and before suffix.
129
- # @example
130
- # source code of the ast node is
131
- # class Foobar
132
- # end
133
- # then we call
134
- # wrap(node, prefix: 'module Synvert', suffix: 'end', newline: true)
135
- # the source code will be rewritten to
136
- # module Synvert
137
- # class Foobar
138
- # end
139
- # end
140
- def wrap(node, prefix:, suffix:, newline: false)
141
- if newline
142
- indentation = @adapter.get_start_loc(node).column
143
- group do
144
- insert node, prefix + "\n" + (' ' * indentation), at: 'beginning'
145
- insert node, "\n" + (' ' * indentation) + suffix, at: 'end'
146
- indent node
147
- end
148
- else
149
- group do
150
- insert node, prefix, at: 'beginning'
151
- insert node, suffix, at: 'end'
152
- end
153
- end
154
- end
155
-
156
- # Indent source code of the ast node
157
- # @param node [Node] ast node
158
- # @example
159
- # source code of ast node is
160
- # class Foobar
161
- # end
162
- # then we call
163
- # indent(node)
164
- # the source code will be rewritten to
165
- # class Foobar
166
- # end
167
- def indent(node)
168
- @actions << NodeMutation::IndentAction.new(node, adapter: @adapter).process
169
- end
170
-
171
- # No operation.
172
- # @param node [Node] ast node
173
- def noop(node)
174
- @actions << NodeMutation::NoopAction.new(node, adapter: @adapter).process
175
- end
176
-
177
- # group multiple actions
178
- def group(&block)
179
- @actions << NodeMutation::GroupAction.new(adapter: @adapter, &block).process
180
- end
181
- end