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 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