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 +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +9 -6
- data/lib/node_mutation/action/group_action.rb +1 -10
- data/lib/node_mutation/adapter/prism.rb +242 -0
- data/lib/node_mutation/adapter/syntax_tree.rb +15 -15
- data/lib/node_mutation/version.rb +1 -1
- data/lib/node_mutation.rb +177 -3
- metadata +3 -3
- data/lib/node_mutation/actionable.rb +0 -181
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 667309a875501329f61441ddc89143500b02d87e83be0a6aa324e78438cfd397
|
|
4
|
+
data.tar.gz: 565be945f6bbfd0976dafb86bb346be696665bd32a72227cf9a4a2d16e75fa45
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed08dbed41c4aa8dce675995a19736ce84877fc0334ab448c04019c7e413204ee2e0c6674ea2d779048962bccbf970990d0b72f89fb7bc17845cb0034b6400ce
|
|
7
|
+
data.tar.gz: 8488422fbcabd15c24c607c1a2b8229bf3a7be810a2d34f0568ceb79a82cc87ebdd06625ab0d76cf68099fd894a067267fc0dde257f5999aa48a8f0ef1c40346
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
node_mutation (1.
|
|
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.
|
|
37
|
+
parser (3.3.0.5)
|
|
38
38
|
ast (~> 2.4.1)
|
|
39
39
|
racc
|
|
40
|
-
parser_node_ext (1.2.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
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.
|
|
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-
|
|
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
|