node_mutation 1.18.3 → 1.19.0

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: f0f3e24e2bcc4c8de744080aeccf3b82741534b6d5681c582d4ea91318b156b5
4
+ data.tar.gz: 33a39316e45592619c5ad3d520c6cb42411e41b6704bc1c52d840e0414c46322
5
5
  SHA512:
6
- metadata.gz: a67239ac105f6ba64aa52a67c228310e4b13a8b2f0076343f006faf562c0a4cf00d3eb83212fdd75d3c63421c8763a67a1afb2c067dceaf9e1fcd25d8fb935e5
7
- data.tar.gz: 0b6c56d6114b997743a33e96c19976ceb9cdc15c0e1b515acf092e1275149c5ddb93359707700cca9b274decf1e73690ed12b23c8280eccda1500e9a5e88ccf4
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.18.3)
4
+ node_mutation (1.19.0)
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,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.include?('(') && direct_child_name.include?(')')
214
- child_node = node.instance_eval(direct_child_name)
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.include?('(') && direct_child_name.include?(')')
113
- child_node = node.instance_eval(direct_child_name)
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
@@ -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.0"
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.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-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