node_mutation 1.18.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66c10f74e41d63887a03af240cdfe99086395505ef43e14dab21f9d7ece562ac
4
- data.tar.gz: 73728f0f84e095b7f97b65511cbd75fc01bcb7b8e9f2a56965dda31bfb7da622
3
+ metadata.gz: f0f3e24e2bcc4c8de744080aeccf3b82741534b6d5681c582d4ea91318b156b5
4
+ data.tar.gz: 33a39316e45592619c5ad3d520c6cb42411e41b6704bc1c52d840e0414c46322
5
5
  SHA512:
6
- metadata.gz: 030fff9b3477f6c8f35454f01cf24f430b1bcabd9f2002c97ebb6f7579db777432e3cddd40bb2c188bcedfab408c255b8aa554a03410df375b51e0ad498d6240
7
- data.tar.gz: 34078dd910cacc649c23334d13dea818d728cef40d7b130325b204bfb34ec46a07691bb98c94c6223c5d40cf808db1e83002473907ca94dededc661652b0bf85
6
+ metadata.gz: 0064ba628586533f3687a2af1cdbfbd0af32a274bc127e7da97d72c5b4d7517c1f0a8658fba363762ede790b3ee1071fed6b702e75932f5e57719ff0a6f7c023
7
+ data.tar.gz: 231809db15865e3c260b2aa67edf5335968968299b5e5135571a12631bb90ed563fdd1a9cd59996d909acee42b4770b1ca98c58734fcee26ea1255a2eb525371
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
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
+
14
+ ## 1.18.3 (2023-06-03)
15
+
16
+ * Fix rbs syntax
17
+
3
18
  ## 1.18.2 (2023-05-21)
4
19
 
5
20
  * Use `rindex` to calculate "do" index
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.18.2)
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
@@ -17,7 +17,8 @@ class NodeMutation::IndentAction < NodeMutation::Action
17
17
  # @return [String] rewritten code.
18
18
  def new_code
19
19
  source = NodeMutation.adapter.get_source(@node)
20
- source.each_line.map { |line| ' ' * NodeMutation.tab_width * @tab_size + line }.join
20
+ source.each_line.map { |line| (' ' * NodeMutation.tab_width * @tab_size) + line }
21
+ .join
21
22
  end
22
23
 
23
24
  private
@@ -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,25 +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
- when %i[class name], %i[const name], %i[cvar name], %i[def name], %i[defs name], %i[gvar name], %i[ivar name], %i[lvar name]
174
+ when %i[class name], %i[const name], %i[cvar name], %i[def name], %i[defs name],
175
+ %i[gvar name], %i[ivar name], %i[lvar name]
110
176
  NodeMutation::Struct::Range.new(node.loc.name.begin_pos, node.loc.name.end_pos)
111
177
  when %i[const double_colon]
112
178
  NodeMutation::Struct::Range.new(node.loc.double_colon.begin_pos, node.loc.double_colon.end_pos)
@@ -208,8 +274,25 @@ class NodeMutation::ParserAdapter < NodeMutation::Adapter
208
274
 
209
275
  if node.respond_to?(direct_child_name)
210
276
  child_node = node.send(direct_child_name)
211
- elsif direct_child_name.include?('(') && direct_child_name.include?(')')
212
- 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} }"
213
296
  else
214
297
  raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
215
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
@@ -19,7 +19,10 @@ class NodeMutation::Result
19
19
  end
20
20
 
21
21
  def actions=(actions)
22
- @actions = actions.map { |action| NodeMutation::Struct::Action.new(action.type, action.start, action.end, action.new_code) }
22
+ @actions =
23
+ actions.map { |action|
24
+ NodeMutation::Struct::Action.new(action.type, action.start, action.end, action.new_code)
25
+ }
23
26
  end
24
27
 
25
28
  def to_json(*args)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeMutation
4
- VERSION = "1.18.2"
4
+ VERSION = "1.19.0"
5
5
  end
data/lib/node_mutation.rb CHANGED
@@ -208,8 +208,8 @@ class NodeMutation
208
208
  def wrap(node, prefix:, suffix:, newline: false)
209
209
  if newline
210
210
  indentation = NodeMutation.adapter.get_start_loc(node).column
211
- @actions << InsertAction.new(node, prefix + "\n" + ' ' * indentation, at: 'beginning').process
212
- @actions << InsertAction.new(node, "\n" + ' ' * indentation + suffix, at: 'end').process
211
+ @actions << InsertAction.new(node, prefix + "\n" + (' ' * indentation), at: 'beginning').process
212
+ @actions << InsertAction.new(node, "\n" + (' ' * indentation) + suffix, at: 'end').process
213
213
  @actions << IndentAction.new(node).process
214
214
  else
215
215
  @actions << InsertAction.new(node, prefix, at: 'beginning').process
@@ -1,18 +1,18 @@
1
1
  class NodeMutation::Struct
2
2
  class Action < ::Struct
3
- prop :type, Symbol
4
- prop :start, Integer
5
- prop :end, Integer
6
- prop :new_code, String
3
+ atr_accessor type (): Symbol
4
+ atr_accessor start (): Integer
5
+ atr_accessor end (): Integer
6
+ atr_accessor new_code (): String
7
7
  end
8
8
 
9
9
  class Location < ::Struct
10
- prop :line, Integer
11
- prop :column, Integer
10
+ atr_accessor line (): Integer
11
+ atr_accessor column (): Integer
12
12
  end
13
13
 
14
14
  class Range < ::Struct
15
- prop :start, Integer
16
- prop :end, Integer
15
+ atr_accessor start (): Integer
16
+ atr_accessor end (): Integer
17
17
  end
18
18
  end
@@ -21,7 +21,7 @@ module NodeMutation[T]
21
21
 
22
22
  def append: (node: T, code: String) -> void
23
23
 
24
- def delete: (node: T, *selectors: Array[String], **options: { and_comma: bool }) -> void
24
+ def delete: (node: T, selectors: Array[String], and_comma: bool) -> void
25
25
 
26
26
  def indent: (node: T, ?tab_size: Integer) -> void
27
27
 
@@ -29,9 +29,9 @@ module NodeMutation[T]
29
29
 
30
30
  def prepend: (node: T, code: String) -> void
31
31
 
32
- def remove: (node: T, **options: { and_comma: bool }) -> void
32
+ def remove: (node: T, and_comma: bool) -> void
33
33
 
34
- def replace: (node: T, *selectors: Array[String], with: String) -> void
34
+ def replace: (node: T, selectors: Array[String], with: String) -> void
35
35
 
36
36
  def replace_with: (node: T, code: String) -> void
37
37
 
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.2
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-05-21 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