node_mutation 1.18.3 → 1.19.1
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 +15 -0
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/lib/node_mutation/adapter/parser.rb +108 -21
- data/lib/node_mutation/adapter/syntax_tree.rb +78 -2
- data/lib/node_mutation/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07cffea51271875fe053793222f92ba3ed88fb977e90c875d7f6096b969de898
|
4
|
+
data.tar.gz: f532736d3c384703dca1cfa389b4cad86fb810d8b063540ad50774af649786c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c900a66575d3c35780bf64f33aa3452a5cd558ba7e55cde46f0c322f5da8cf54f17b052683a71df74c51df7efda8e3626e451f987fd1ea5f3995afd3f72635f4
|
7
|
+
data.tar.gz: f2335ad6e3ad0a5dcd1ec36d0f595c114d703dea847f36d6b1235a4ceb1cc92e6fe27522cd941da0e34e3864ccb9abd169f9f83e02a9306ab6075f8d2ea1c09d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# NodeMutation
|
2
2
|
|
3
|
+
## 1.19.1 (2023-06-22)
|
4
|
+
|
5
|
+
* Add `to_string` function
|
6
|
+
|
7
|
+
## 1.19.0 (2023-06-22)
|
8
|
+
|
9
|
+
* Drop support for function in `child_node_by_name`
|
10
|
+
* Add `to_symbol` function
|
11
|
+
* Add `to_single_quote` function
|
12
|
+
* Add `to_double_quote` function
|
13
|
+
* Add `to_lambda_literal` function
|
14
|
+
* Add `strip_curly_braces` function
|
15
|
+
* Add `wrap_curly_braces` function
|
16
|
+
* Add more comments
|
17
|
+
|
3
18
|
## 1.18.3 (2023-06-03)
|
4
19
|
|
5
20
|
* Fix rbs syntax
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -65,6 +65,19 @@ result.conflicted
|
|
65
65
|
result.new_source
|
66
66
|
```
|
67
67
|
|
68
|
+
## Evaluated Value
|
69
|
+
|
70
|
+
NodeMutation supports to evaluate the value of the node, and use the evaluated value to rewrite the source code.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
source = 'after_commit :do_index, on: :create, if: :indexable?'
|
74
|
+
node = Parser::CurrentRuby.parse(source)
|
75
|
+
mutation.replace node, '{{arguments.-1.on_value}}', with: ':update'
|
76
|
+
source # after_commit :do_index, on: :update, if: :indexable?
|
77
|
+
```
|
78
|
+
|
79
|
+
See more in [ParserAdapter](https://xinminlabs.github.io/node-mutation-ruby/NodeMutation/ParserAdapter.html) and [SyntaxTreeAdapter](https://xinminlabs.github.io/node-mutation-ruby/NodeMutation/SyntaxTreeAdapter.html)
|
80
|
+
|
68
81
|
## Configuration
|
69
82
|
|
70
83
|
### adapter
|
@@ -15,6 +15,53 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
# It gets the new source code after evaluating the node.
|
19
|
+
# @param node [Parser::AST::Node] The node to evaluate.
|
20
|
+
# @param code [String] The code to evaluate.
|
21
|
+
# @return [String] The new source code.
|
22
|
+
# @example
|
23
|
+
# node = Parser::CurrentRuby.parse('Factory.define :user do; end')
|
24
|
+
# rewritten_source(node, '{{call.receiver}}').to eq 'Factory'
|
25
|
+
#
|
26
|
+
# # index for node array
|
27
|
+
# node = Parser::CurrentRuby.parse("test(foo, bar)")
|
28
|
+
# rewritten_source(node, '{{arguments.0}}')).to eq 'foo'
|
29
|
+
#
|
30
|
+
# # {key}_pair for hash node
|
31
|
+
# node = Parser::CurrentRuby.parse("after_commit :do_index, on: :create, if: :indexable?")
|
32
|
+
# rewritten_source(node, '{{arguments.-1.on_pair}}')).to eq 'on: :create'
|
33
|
+
#
|
34
|
+
# # {key}_value for hash node
|
35
|
+
# node = Parser::CurrentRuby.parse("after_commit :do_index, on: :create, if: :indexable?")
|
36
|
+
# rewritten_source(node, '{{arguments.-1.on_value}}')).to eq ':create'
|
37
|
+
#
|
38
|
+
# # to_single_quote for str node
|
39
|
+
# node = Parser::CurrentRuby.parse('"foo"')
|
40
|
+
# rewritten_source(node, 'to_single_quote') => "'foo'"
|
41
|
+
#
|
42
|
+
# # to_double_quote for str node
|
43
|
+
# node = Parser::CurrentRuby.parse("'foo'")
|
44
|
+
# rewritten_source(node, 'to_double_quote') => '"foo"'
|
45
|
+
#
|
46
|
+
# # to_symbol for str node
|
47
|
+
# node = Parser::CurrentRuby.parse("'foo'")
|
48
|
+
# rewritten_source(node, 'to_symbol') => ':foo'
|
49
|
+
#
|
50
|
+
# # to_string for sym node
|
51
|
+
# node = Parser::CurrentRuby.parse(":foo")
|
52
|
+
# rewritten_source(node, 'to_string') => 'foo'
|
53
|
+
#
|
54
|
+
# # to_lambda_literal for block node
|
55
|
+
# node = Parser::CurrentRuby.parse('lambda { foobar }')
|
56
|
+
# rewritten_source(node, 'to_lambda_literal') => '-> { foobar }'
|
57
|
+
#
|
58
|
+
# # strip_curly_braces for hash node
|
59
|
+
# node = Parser::CurrentRuby.parse("{ foo: 'bar' }")
|
60
|
+
# rewritten_source(node, 'strip_curly_braces') => "foo: 'bar'"
|
61
|
+
#
|
62
|
+
# # wrap_curly_braces for hash node
|
63
|
+
# node = Parser::CurrentRuby.parse("test(foo: 'bar')")
|
64
|
+
# rewritten_source(node.arguments.first, 'wrap_curly_braces') => "{ foo: 'bar' }"
|
18
65
|
def rewritten_source(node, code)
|
19
66
|
code.gsub(/{{(.+?)}}/m) do
|
20
67
|
old_code = Regexp.last_match(1)
|
@@ -56,6 +103,46 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
56
103
|
node.loc.expression.source_buffer.source
|
57
104
|
end
|
58
105
|
|
106
|
+
# Get the range of the child node.
|
107
|
+
# @param node [Parser::AST::Node] The node.
|
108
|
+
# @param child_name [String] THe name to find child node.
|
109
|
+
# @return {NodeMutation::Struct::Range} The range of the child node.
|
110
|
+
# @example
|
111
|
+
# node = Parser::CurrentRuby.parse('Factory.define :user do; end')
|
112
|
+
# child_node_range(node, 'caller.receiver') => { start: 0, end: 'Factory'.length }
|
113
|
+
#
|
114
|
+
# # node array
|
115
|
+
# node = Parser::CurrentRuby.parse('foobar arg1, arg2)')
|
116
|
+
# child_node_range(node, 'arguments') => { start: 'foobar '.length, end: 'foobar arg1, arg2'.length }
|
117
|
+
#
|
118
|
+
# # index for node array
|
119
|
+
# node = Parser::CurrentRuby.parse('foobar(arg1, arg2)')
|
120
|
+
# child_node_range(node, 'arguments.-1') => { start: 'foobar(arg1, '.length, end: 'foobar(arg1, arg2'.length }
|
121
|
+
#
|
122
|
+
# # pips for block node
|
123
|
+
# node = Parser::CurrentRuby.parse('Factory.define :user do |user|; end')
|
124
|
+
# child_node_range(node, 'pipes') => { start: 'Factory.deine :user do '.length, end: 'Factory.define :user do |user|'.length }
|
125
|
+
#
|
126
|
+
# # parentheses for def and defs node
|
127
|
+
# node = Parser::CurrentRuby.parse('def foo(bar); end')
|
128
|
+
# child_node_range(node, 'parentheses') => { start: 'def foo'.length, end: 'def foo(bar)'.length }
|
129
|
+
#
|
130
|
+
# # double_colon for const node
|
131
|
+
# node = Parser::CurrentRuby.parse('Foo::Bar')
|
132
|
+
# child_node_range(node, 'double_colon') => { start: 'Foo'.length, end: 'Foo::'.length }
|
133
|
+
#
|
134
|
+
# # self and dot for defs node
|
135
|
+
# node = Parser::CurrentRuby.parse('def self.foo(bar); end')
|
136
|
+
# child_node_range(node, 'self') => { start: 'def '.length, end: 'def self'.length }
|
137
|
+
# child_node_range(node, 'dot') => { start: 'def self'.length, end: 'def self.'.length }
|
138
|
+
#
|
139
|
+
# # dot for send and csend node
|
140
|
+
# node = Parser::CurrentRuby.parse('foo.bar(test)')
|
141
|
+
# child_node_range(node, 'self') => { start: 'foo'.length, end: 'foo.'.length }
|
142
|
+
#
|
143
|
+
# # parentheses for send and csend node
|
144
|
+
# node = Parser::CurrentRuby.parse('foo.bar(test)')
|
145
|
+
# child_node_range(node, 'parentheses') => { start: 'foo.bar'.length, end: 'foo.bar(test)'.length }
|
59
146
|
def child_node_range(node, child_name)
|
60
147
|
direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
|
61
148
|
|
@@ -88,27 +175,8 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
88
175
|
node.arguments.last.loc.expression.end_pos + 1
|
89
176
|
)
|
90
177
|
end
|
91
|
-
when %i[block arguments], %i[def arguments], %i[defs arguments]
|
92
|
-
if node.arguments.empty?
|
93
|
-
nil
|
94
|
-
else
|
95
|
-
NodeMutation::Struct::Range.new(
|
96
|
-
node.arguments.first.loc.expression.begin_pos,
|
97
|
-
node.arguments.last.loc.expression.end_pos
|
98
|
-
)
|
99
|
-
end
|
100
|
-
when %i[block body], %i[class body], %i[def body], %i[defs body], %i[module body]
|
101
|
-
if node.body.empty?
|
102
|
-
nil
|
103
|
-
else
|
104
|
-
NodeMutation::Struct::Range.new(
|
105
|
-
node.body.first.loc.expression.begin_pos,
|
106
|
-
node.body.last.loc.expression.end_pos
|
107
|
-
)
|
108
|
-
end
|
109
178
|
when %i[class name], %i[const name], %i[cvar name], %i[def name], %i[defs name],
|
110
179
|
%i[gvar name], %i[ivar name], %i[lvar name]
|
111
|
-
|
112
180
|
NodeMutation::Struct::Range.new(node.loc.name.begin_pos, node.loc.name.end_pos)
|
113
181
|
when %i[const double_colon]
|
114
182
|
NodeMutation::Struct::Range.new(node.loc.double_colon.begin_pos, node.loc.double_colon.end_pos)
|
@@ -210,8 +278,27 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
210
278
|
|
211
279
|
if node.respond_to?(direct_child_name)
|
212
280
|
child_node = node.send(direct_child_name)
|
213
|
-
elsif direct_child_name
|
214
|
-
child_node = node.
|
281
|
+
elsif direct_child_name == 'to_symbol' && node.type == :str
|
282
|
+
child_node = ":#{node.to_value}"
|
283
|
+
elsif direct_child_name == 'to_string' && node.type == :sym
|
284
|
+
child_node = node.to_value.to_s
|
285
|
+
elsif direct_child_name == 'to_single_quote' && node.type == :str
|
286
|
+
child_node = "'#{node.to_value}'"
|
287
|
+
elsif direct_child_name == 'to_double_quote' && node.type == :str
|
288
|
+
child_node = "\"#{node.to_value}\""
|
289
|
+
elsif direct_child_name == 'to_lambda_literal' && node.type == :block && node.caller.type == :send && node.caller.receiver.nil? && node.caller.message == :lambda
|
290
|
+
new_source = node.to_source
|
291
|
+
if node.arguments.size > 1
|
292
|
+
new_source = new_source[0...node.arguments.first.loc.expression.begin_pos - 2] + new_source[node.arguments.last.loc.expression.end_pos + 1..-1]
|
293
|
+
new_source = new_source.sub('lambda', "->(#{node.arguments.map(&:to_source).join(', ')})")
|
294
|
+
else
|
295
|
+
new_source = new_source.sub('lambda', '->')
|
296
|
+
end
|
297
|
+
child_node = new_source
|
298
|
+
elsif direct_child_name == 'strip_curly_braces' && node.type == :hash
|
299
|
+
child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
|
300
|
+
elsif direct_child_name == 'wrap_curly_braces' && node.type == :hash
|
301
|
+
child_node = "{ #{node.to_source} }"
|
215
302
|
else
|
216
303
|
raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
|
217
304
|
end
|
@@ -12,6 +12,53 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
|
|
12
12
|
node.source[node.location.start_char...node.location.end_char]
|
13
13
|
end
|
14
14
|
|
15
|
+
# It gets the new source code after evaluating the node.
|
16
|
+
# @param node [SyntaxTree::Node] The node to evaluate.
|
17
|
+
# @param code [String] The code to evaluate.
|
18
|
+
# @return [String] The new source code.
|
19
|
+
# @example
|
20
|
+
# node = SyntaxTree::Parser.new('class Synvert; end').parse.statements.body.first
|
21
|
+
# rewritten_source(node, '{{constant}}').to eq 'Synvert'
|
22
|
+
#
|
23
|
+
# # index for node array
|
24
|
+
# node = SyntaxTree::Parser.new("foo.bar(a, b)").parse.statements.body.first
|
25
|
+
# rewritten_source(node, '{{arguments.arguments.parts.-1}}')).to eq 'b'
|
26
|
+
#
|
27
|
+
# # {key}_assoc for HashLiteral node
|
28
|
+
# node = SyntaxTree::Parser.new("after_commit :do_index, on: :create, if: :indexable?").parse.statements.body.first
|
29
|
+
# rewritten_source(node, '{{arguments.parts.-1.on_assoc}}')).to eq 'on: :create'
|
30
|
+
#
|
31
|
+
# # {key}_value for hash node
|
32
|
+
# node = SyntaxTree::Parser.new("after_commit :do_index, on: :create, if: :indexable?").parse.statements.body.first
|
33
|
+
# rewritten_source(node, '{{arguments.parts.-1.on_value}}')).to eq ':create'
|
34
|
+
#
|
35
|
+
# # to_single_quote for StringLiteral node
|
36
|
+
# node = SyntaxTree::Parser.new('"foo"').parse.statements.body.first
|
37
|
+
# rewritten_source(node, 'to_single_quote') => "'foo'"
|
38
|
+
#
|
39
|
+
# # to_double_quote for StringLiteral node
|
40
|
+
# node = SyntaxTree::Parser.new("'foo'").parse.statements.body.first
|
41
|
+
# rewritten_source(node, 'to_double_quote') => '"foo"'
|
42
|
+
#
|
43
|
+
# # to_symbol for StringLiteral node
|
44
|
+
# node = SyntaxTree::Parser.new("'foo'").parse.statements.body.first
|
45
|
+
# rewritten_source(node, 'to_symbol') => ':foo'
|
46
|
+
#
|
47
|
+
# # to_string for SymbolLiteral node
|
48
|
+
# node = SyntaxTree::Parser.new(":foo").parse.statements.body.first
|
49
|
+
# rewritten_source(node, 'to_string') => 'foo'
|
50
|
+
#
|
51
|
+
# # to_lambda_literal for MethodAddBlock node
|
52
|
+
# node = SyntaxTree::Parser.new('lambda { foobar }').parse.statements.body.first
|
53
|
+
# rewritten_source(node, 'to_lambda_literal') => '-> { foobar }'
|
54
|
+
#
|
55
|
+
# # strip_curly_braces for HashLiteral node
|
56
|
+
# node = SyntaxTree::Parser.new("{ foo: 'bar' }").parse.statements.body.first
|
57
|
+
# rewritten_source(node, 'strip_curly_braces') => "foo: 'bar'"
|
58
|
+
#
|
59
|
+
# # wrap_curly_braces for BareAssocHash node
|
60
|
+
# node = SyntaxTree::Parser.new("test(foo: 'bar')").parse.statements.body.first
|
61
|
+
# rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') => "{ foo: 'bar' }"
|
15
62
|
def rewritten_source(node, code)
|
16
63
|
code.gsub(/{{(.+?)}}/m) do
|
17
64
|
old_code = Regexp.last_match(1)
|
@@ -48,6 +95,19 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
|
|
48
95
|
node.source
|
49
96
|
end
|
50
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 = SyntaxTree::Parser.new('foo.bar(test)').parse.statements.body.first
|
104
|
+
# child_node_range(node, 'receiver') => { start: 0, end: 'foo'.length }
|
105
|
+
# node array
|
106
|
+
# node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
|
107
|
+
# child_node_range(node, 'arguments.arguments') => { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }
|
108
|
+
# index for node array
|
109
|
+
# node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
|
110
|
+
# child_node_range(node, 'arguments.arguments.parts.-1') => { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }
|
51
111
|
def child_node_range(node, child_name)
|
52
112
|
child_node = child_node_by_name(node, child_name)
|
53
113
|
return nil if child_node.nil?
|
@@ -109,8 +169,24 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
|
|
109
169
|
|
110
170
|
if node.respond_to?(direct_child_name)
|
111
171
|
child_node = node.send(direct_child_name)
|
112
|
-
elsif direct_child_name
|
113
|
-
child_node = node.
|
172
|
+
elsif direct_child_name == 'to_symbol' && node.is_a?(SyntaxTree::StringLiteral)
|
173
|
+
child_node = ":#{node.to_value}"
|
174
|
+
elsif direct_child_name == 'to_string' && node.is_a?(SyntaxTree::SymbolLiteral)
|
175
|
+
child_node = node.to_value.to_s
|
176
|
+
elsif direct_child_name == 'to_single_quote' && node.is_a?(SyntaxTree::StringLiteral)
|
177
|
+
child_node = "'#{node.to_value}'"
|
178
|
+
elsif direct_child_name == 'to_double_quote' && node.is_a?(SyntaxTree::StringLiteral)
|
179
|
+
child_node = "\"#{node.to_value}\""
|
180
|
+
elsif direct_child_name == 'to_lambda_literal' && node.is_a?(SyntaxTree::MethodAddBlock) && node.call.message.value == 'lambda'
|
181
|
+
if node.block.block_var
|
182
|
+
child_node = "->(#{node.block.block_var.params.to_source}) {#{node.block.bodystmt.to_source}}"
|
183
|
+
else
|
184
|
+
child_node = "-> {#{node.block.bodystmt.to_source }}"
|
185
|
+
end
|
186
|
+
elsif direct_child_name == 'strip_curly_braces' && node.is_a?(SyntaxTree::HashLiteral)
|
187
|
+
child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
|
188
|
+
elsif direct_child_name == 'wrap_curly_braces' && node.is_a?(SyntaxTree::BareAssocHash)
|
189
|
+
child_node = "{ #{node.to_source} }"
|
114
190
|
else
|
115
191
|
raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
|
116
192
|
end
|
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.19.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: ast node mutation apis
|
14
14
|
email:
|
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
70
|
- !ruby/object:Gem::Version
|
71
71
|
version: '0'
|
72
72
|
requirements: []
|
73
|
-
rubygems_version: 3.4.
|
73
|
+
rubygems_version: 3.4.13
|
74
74
|
signing_key:
|
75
75
|
specification_version: 4
|
76
76
|
summary: ast node mutation apis
|