node_mutation 1.22.3 → 1.23.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: 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