node_mutation 1.18.3 → 1.19.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 +11 -0
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/lib/node_mutation/adapter/parser.rb +102 -21
- data/lib/node_mutation/adapter/syntax_tree.rb +72 -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: f0f3e24e2bcc4c8de744080aeccf3b82741534b6d5681c582d4ea91318b156b5
|
4
|
+
data.tar.gz: 33a39316e45592619c5ad3d520c6cb42411e41b6704bc1c52d840e0414c46322
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0064ba628586533f3687a2af1cdbfbd0af32a274bc127e7da97d72c5b4d7517c1f0a8658fba363762ede790b3ee1071fed6b702e75932f5e57719ff0a6f7c023
|
7
|
+
data.tar.gz: 231809db15865e3c260b2aa67edf5335968968299b5e5135571a12631bb90ed563fdd1a9cd59996d909acee42b4770b1ca98c58734fcee26ea1255a2eb525371
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# NodeMutation
|
2
2
|
|
3
|
+
## 1.19.0 (2023-06-22)
|
4
|
+
|
5
|
+
* Drop support for function in `child_node_by_name`
|
6
|
+
* Add `to_symbol` function
|
7
|
+
* Add `to_single_quote` function
|
8
|
+
* Add `to_double_quote` function
|
9
|
+
* Add `to_lambda_literal` function
|
10
|
+
* Add `strip_curly_braces` function
|
11
|
+
* Add `wrap_curly_braces` function
|
12
|
+
* Add more comments
|
13
|
+
|
3
14
|
## 1.18.3 (2023-06-03)
|
4
15
|
|
5
16
|
* 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,49 @@ 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_lambda_literal for block node
|
51
|
+
# node = Parser::CurrentRuby.parse('lambda { foobar }')
|
52
|
+
# rewritten_source(node, 'to_lambda_literal') => '-> { foobar }'
|
53
|
+
#
|
54
|
+
# # strip_curly_braces for hash node
|
55
|
+
# node = Parser::CurrentRuby.parse("{ foo: 'bar' }")
|
56
|
+
# rewritten_source(node, 'strip_curly_braces') => "foo: 'bar'"
|
57
|
+
#
|
58
|
+
# # wrap_curly_braces for hash node
|
59
|
+
# node = Parser::CurrentRuby.parse("test(foo: 'bar')")
|
60
|
+
# rewritten_source(node.arguments.first, 'wrap_curly_braces') => "{ foo: 'bar' }"
|
18
61
|
def rewritten_source(node, code)
|
19
62
|
code.gsub(/{{(.+?)}}/m) do
|
20
63
|
old_code = Regexp.last_match(1)
|
@@ -56,6 +99,46 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
56
99
|
node.loc.expression.source_buffer.source
|
57
100
|
end
|
58
101
|
|
102
|
+
# Get the range of the child node.
|
103
|
+
# @param node [Parser::AST::Node] The node.
|
104
|
+
# @param child_name [String] THe name to find child node.
|
105
|
+
# @return {NodeMutation::Struct::Range} The range of the child node.
|
106
|
+
# @example
|
107
|
+
# node = Parser::CurrentRuby.parse('Factory.define :user do; end')
|
108
|
+
# child_node_range(node, 'caller.receiver') => { start: 0, end: 'Factory'.length }
|
109
|
+
#
|
110
|
+
# # node array
|
111
|
+
# node = Parser::CurrentRuby.parse('foobar arg1, arg2)')
|
112
|
+
# child_node_range(node, 'arguments') => { start: 'foobar '.length, end: 'foobar arg1, arg2'.length }
|
113
|
+
#
|
114
|
+
# # index for node array
|
115
|
+
# node = Parser::CurrentRuby.parse('foobar(arg1, arg2)')
|
116
|
+
# child_node_range(node, 'arguments.-1') => { start: 'foobar(arg1, '.length, end: 'foobar(arg1, arg2'.length }
|
117
|
+
#
|
118
|
+
# # pips for block node
|
119
|
+
# node = Parser::CurrentRuby.parse('Factory.define :user do |user|; end')
|
120
|
+
# child_node_range(node, 'pipes') => { start: 'Factory.deine :user do '.length, end: 'Factory.define :user do |user|'.length }
|
121
|
+
#
|
122
|
+
# # parentheses for def and defs node
|
123
|
+
# node = Parser::CurrentRuby.parse('def foo(bar); end')
|
124
|
+
# child_node_range(node, 'parentheses') => { start: 'def foo'.length, end: 'def foo(bar)'.length }
|
125
|
+
#
|
126
|
+
# # double_colon for const node
|
127
|
+
# node = Parser::CurrentRuby.parse('Foo::Bar')
|
128
|
+
# child_node_range(node, 'double_colon') => { start: 'Foo'.length, end: 'Foo::'.length }
|
129
|
+
#
|
130
|
+
# # self and dot for defs node
|
131
|
+
# node = Parser::CurrentRuby.parse('def self.foo(bar); end')
|
132
|
+
# child_node_range(node, 'self') => { start: 'def '.length, end: 'def self'.length }
|
133
|
+
# child_node_range(node, 'dot') => { start: 'def self'.length, end: 'def self.'.length }
|
134
|
+
#
|
135
|
+
# # dot for send and csend node
|
136
|
+
# node = Parser::CurrentRuby.parse('foo.bar(test)')
|
137
|
+
# child_node_range(node, 'self') => { start: 'foo'.length, end: 'foo.'.length }
|
138
|
+
#
|
139
|
+
# # parentheses for send and csend node
|
140
|
+
# node = Parser::CurrentRuby.parse('foo.bar(test)')
|
141
|
+
# child_node_range(node, 'parentheses') => { start: 'foo.bar'.length, end: 'foo.bar(test)'.length }
|
59
142
|
def child_node_range(node, child_name)
|
60
143
|
direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
|
61
144
|
|
@@ -88,27 +171,8 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
88
171
|
node.arguments.last.loc.expression.end_pos + 1
|
89
172
|
)
|
90
173
|
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
174
|
when %i[class name], %i[const name], %i[cvar name], %i[def name], %i[defs name],
|
110
175
|
%i[gvar name], %i[ivar name], %i[lvar name]
|
111
|
-
|
112
176
|
NodeMutation::Struct::Range.new(node.loc.name.begin_pos, node.loc.name.end_pos)
|
113
177
|
when %i[const double_colon]
|
114
178
|
NodeMutation::Struct::Range.new(node.loc.double_colon.begin_pos, node.loc.double_colon.end_pos)
|
@@ -210,8 +274,25 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
|
|
210
274
|
|
211
275
|
if node.respond_to?(direct_child_name)
|
212
276
|
child_node = node.send(direct_child_name)
|
213
|
-
elsif direct_child_name
|
214
|
-
child_node = node.
|
277
|
+
elsif direct_child_name == 'to_symbol' && node.type == :str
|
278
|
+
child_node = ":#{node.to_value}"
|
279
|
+
elsif direct_child_name == 'to_single_quote' && node.type == :str
|
280
|
+
child_node = "'#{node.to_value}'"
|
281
|
+
elsif direct_child_name == 'to_double_quote' && node.type == :str
|
282
|
+
child_node = "\"#{node.to_value}\""
|
283
|
+
elsif direct_child_name == 'to_lambda_literal' && node.type == :block && node.caller.type == :send && node.caller.receiver.nil? && node.caller.message == :lambda
|
284
|
+
new_source = node.to_source
|
285
|
+
if node.arguments.size > 1
|
286
|
+
new_source = new_source[0...node.arguments.first.loc.expression.begin_pos - 2] + new_source[node.arguments.last.loc.expression.end_pos + 1..-1]
|
287
|
+
new_source = new_source.sub('lambda', "->(#{node.arguments.map(&:to_source).join(', ')})")
|
288
|
+
else
|
289
|
+
new_source = new_source.sub('lambda', '->')
|
290
|
+
end
|
291
|
+
child_node = new_source
|
292
|
+
elsif direct_child_name == 'strip_curly_braces' && node.type == :hash
|
293
|
+
child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
|
294
|
+
elsif direct_child_name == 'wrap_curly_braces' && node.type == :hash
|
295
|
+
child_node = "{ #{node.to_source} }"
|
215
296
|
else
|
216
297
|
raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
|
217
298
|
end
|
@@ -12,6 +12,49 @@ 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_lambda_literal for MethodAddBlock node
|
48
|
+
# node = SyntaxTree::Parser.new('lambda { foobar }').parse.statements.body.first
|
49
|
+
# rewritten_source(node, 'to_lambda_literal') => '-> { foobar }'
|
50
|
+
#
|
51
|
+
# # strip_curly_braces for HashLiteral node
|
52
|
+
# node = SyntaxTree::Parser.new("{ foo: 'bar' }").parse.statements.body.first
|
53
|
+
# rewritten_source(node, 'strip_curly_braces') => "foo: 'bar'"
|
54
|
+
#
|
55
|
+
# # wrap_curly_braces for BareAssocHash node
|
56
|
+
# node = SyntaxTree::Parser.new("test(foo: 'bar')").parse.statements.body.first
|
57
|
+
# rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') => "{ foo: 'bar' }"
|
15
58
|
def rewritten_source(node, code)
|
16
59
|
code.gsub(/{{(.+?)}}/m) do
|
17
60
|
old_code = Regexp.last_match(1)
|
@@ -48,6 +91,19 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
|
|
48
91
|
node.source
|
49
92
|
end
|
50
93
|
|
94
|
+
# Get the range of the child node.
|
95
|
+
# @param node [Parser::AST::Node] The node.
|
96
|
+
# @param child_name [String] THe name to find child node.
|
97
|
+
# @return {NodeMutation::Struct::Range} The range of the child node.
|
98
|
+
# @example
|
99
|
+
# node = SyntaxTree::Parser.new('foo.bar(test)').parse.statements.body.first
|
100
|
+
# child_node_range(node, 'receiver') => { start: 0, end: 'foo'.length }
|
101
|
+
# node array
|
102
|
+
# node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
|
103
|
+
# child_node_range(node, 'arguments.arguments') => { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }
|
104
|
+
# index for node array
|
105
|
+
# node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
|
106
|
+
# child_node_range(node, 'arguments.arguments.parts.-1') => { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }
|
51
107
|
def child_node_range(node, child_name)
|
52
108
|
child_node = child_node_by_name(node, child_name)
|
53
109
|
return nil if child_node.nil?
|
@@ -109,8 +165,22 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
|
|
109
165
|
|
110
166
|
if node.respond_to?(direct_child_name)
|
111
167
|
child_node = node.send(direct_child_name)
|
112
|
-
elsif direct_child_name
|
113
|
-
child_node = node.
|
168
|
+
elsif direct_child_name == 'to_symbol' && node.is_a?(SyntaxTree::StringLiteral)
|
169
|
+
child_node = ":#{node.to_value}"
|
170
|
+
elsif direct_child_name == 'to_single_quote' && node.is_a?(SyntaxTree::StringLiteral)
|
171
|
+
child_node = "'#{node.to_value}'"
|
172
|
+
elsif direct_child_name == 'to_double_quote' && node.is_a?(SyntaxTree::StringLiteral)
|
173
|
+
child_node = "\"#{node.to_value}\""
|
174
|
+
elsif direct_child_name == 'to_lambda_literal' && node.is_a?(SyntaxTree::MethodAddBlock) && node.call.message.value == 'lambda'
|
175
|
+
if node.block.block_var
|
176
|
+
child_node = "->(#{node.block.block_var.params.to_source}) {#{node.block.bodystmt.to_source}}"
|
177
|
+
else
|
178
|
+
child_node = "-> {#{node.block.bodystmt.to_source }}"
|
179
|
+
end
|
180
|
+
elsif direct_child_name == 'strip_curly_braces' && node.is_a?(SyntaxTree::HashLiteral)
|
181
|
+
child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
|
182
|
+
elsif direct_child_name == 'wrap_curly_braces' && node.is_a?(SyntaxTree::BareAssocHash)
|
183
|
+
child_node = "{ #{node.to_source} }"
|
114
184
|
else
|
115
185
|
raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
|
116
186
|
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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-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
|