node_mutation 1.18.3 → 1.19.1

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: f841956c32adce81a0124670b81a180dc1a5c607946de843ee4ffb18df46eb59
4
- data.tar.gz: 1f962b025780338e39f674d1a130a42ab85b7ed499630e77151ffb8cf8fcfbdc
3
+ metadata.gz: 07cffea51271875fe053793222f92ba3ed88fb977e90c875d7f6096b969de898
4
+ data.tar.gz: f532736d3c384703dca1cfa389b4cad86fb810d8b063540ad50774af649786c8
5
5
  SHA512:
6
- metadata.gz: a67239ac105f6ba64aa52a67c228310e4b13a8b2f0076343f006faf562c0a4cf00d3eb83212fdd75d3c63421c8763a67a1afb2c067dceaf9e1fcd25d8fb935e5
7
- data.tar.gz: 0b6c56d6114b997743a33e96c19976ceb9cdc15c0e1b515acf092e1275149c5ddb93359707700cca9b274decf1e73690ed12b23c8280eccda1500e9a5e88ccf4
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.18.3)
4
+ node_mutation (1.19.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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.include?('(') && direct_child_name.include?(')')
214
- child_node = node.instance_eval(direct_child_name)
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.include?('(') && direct_child_name.include?(')')
113
- child_node = node.instance_eval(direct_child_name)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeMutation
4
- VERSION = "1.18.3"
4
+ VERSION = "1.19.1"
5
5
  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.18.3
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-03 00:00:00.000000000 Z
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.10
73
+ rubygems_version: 3.4.13
74
74
  signing_key:
75
75
  specification_version: 4
76
76
  summary: ast node mutation apis