node_mutation 1.18.2 → 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: 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