node_mutation 1.22.4 → 1.23.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: 9485dffc945cb3b645d4abecc7f878550685459ccd2007845471651d6893e2f1
4
- data.tar.gz: 0ab63429232abd2f357b2c791bb3e5bc66a4762ff4175d6444b8c51dbac99a5d
3
+ metadata.gz: d39b10154354f3e35da27b09c378c6d518f47e67249b08143bd452dad5c14292
4
+ data.tar.gz: d3f988104df2610c420c7d5622b3f18775e64f878a2978ebc48ca2dbccd3f783
5
5
  SHA512:
6
- metadata.gz: fc326b1af7d3f13bdd91f24c59d3be4991c20cc751de9bf0b8d48be98dc44fa78c93b4a5ffc75eac2535437ad478a206d54342195737989c17d7eb4121624c8a
7
- data.tar.gz: 92cea72b3327755abb2b789cd704809b46258c3a6cc7f7e9f8bac678e2907e93d7fe59e5abfd33308029bc71dee24a1d8e23cbf976f2a1d7d9648876d6e24b78
6
+ metadata.gz: d31f713b129027619f955d8fb96c41562a760d1203333ee6f552f3aed99f76350b0e705649428a249fb63db75c871a562199273f8f21f4956439b7391b9f365c
7
+ data.tar.gz: 16dfcebbf38b0ca70fa02a4e4d5b22ca2915cb2b74e2d77a3b1840ac5bd476bbcbb55d4824e88343fc44f86ee55ed306ea4c6f2e1a022a740660949ccd19a044
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # NodeMutation
2
2
 
3
+ ## 1.23.1 (2024-02-16)
4
+
5
+ * Get `PrismAdapter`
6
+ * Get `CallNode#name` range
7
+
8
+ ## 1.23.0 (2024-02-11)
9
+
10
+ * Support `prism`
11
+
3
12
  ## 1.22.4 (2024-01-30)
4
13
 
5
14
  * Revert "add action methods to GroupAction"
data/Gemfile CHANGED
@@ -9,8 +9,8 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
- gem "parser"
13
12
  gem "parser_node_ext"
14
13
  gem "syntax_tree_ext"
14
+ gem "prism_ext"
15
15
  gem "guard"
16
16
  gem "guard-rspec"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- node_mutation (1.22.4)
4
+ node_mutation (1.23.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -34,16 +34,19 @@ GEM
34
34
  notiffany (0.1.3)
35
35
  nenv (~> 0.1)
36
36
  shellany (~> 0.0)
37
- parser (3.2.2.3)
37
+ parser (3.3.0.5)
38
38
  ast (~> 2.4.1)
39
39
  racc
40
- parser_node_ext (1.2.1)
40
+ parser_node_ext (1.2.2)
41
41
  parser
42
42
  prettier_print (1.2.1)
43
+ prism (0.22.0)
44
+ prism_ext (0.2.1)
45
+ prism
43
46
  pry (0.14.1)
44
47
  coderay (~> 1.1)
45
48
  method_source (~> 1.0)
46
- racc (1.7.1)
49
+ racc (1.7.3)
47
50
  rake (13.0.6)
48
51
  rb-fsevent (0.11.1)
49
52
  rb-inotify (0.10.1)
@@ -62,7 +65,7 @@ GEM
62
65
  rspec-support (~> 3.11.0)
63
66
  rspec-support (3.11.0)
64
67
  shellany (0.0.1)
65
- syntax_tree (6.1.1)
68
+ syntax_tree (6.2.0)
66
69
  prettier_print (>= 1.2.0)
67
70
  syntax_tree_ext (0.6.3)
68
71
  syntax_tree
@@ -78,8 +81,8 @@ DEPENDENCIES
78
81
  guard
79
82
  guard-rspec
80
83
  node_mutation!
81
- parser
82
84
  parser_node_ext
85
+ prism_ext
83
86
  rake (~> 13.0)
84
87
  rspec (~> 3.0)
85
88
  syntax_tree_ext
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'prism'
4
+ require 'prism_ext'
5
+
6
+ class NodeMutation::PrismAdapter < NodeMutation::Adapter
7
+ def get_source(node)
8
+ if node.is_a?(Array)
9
+ return node.first.source[node.first.location.start_offset...node.last.location.end_offset]
10
+ end
11
+
12
+ node.source[node.location.start_offset...node.location.end_offset]
13
+ end
14
+
15
+ # It gets the new source code after evaluating the node.
16
+ # @param node [Prism::Node] The node to evaluate.
17
+ # @param code [String] The code to evaluate.
18
+ # @return [String] The new source code.
19
+ # @example
20
+ # node = Prism.parse('class Synvert; end').value.statements.body.first
21
+ # rewritten_source(node, '{{constant}}') # 'Synvert'
22
+ #
23
+ # # index for node array
24
+ # node = Prism.parse("foo.bar(a, b)").value.statements.body.first
25
+ # rewritten_source(node, '{{arguments.arguments.parts.-1}}')) # 'b'
26
+ #
27
+ # # {key}_assoc for HashNode node
28
+ # node = Prism.parse("after_commit :do_index, on: :create, if: :indexable?").value.statements.body.first
29
+ # rewritten_source(node, '{{arguments.parts.-1.on_assoc}}')) # 'on: :create'
30
+ #
31
+ # # {key}_value for hash node
32
+ # node = Prism.parse("after_commit :do_index, on: :create, if: :indexable?").value.statements.body.first
33
+ # rewritten_source(node, '{{arguments.parts.-1.on_value}}')) # ':create'
34
+ #
35
+ # # to_single_quote for StringNode
36
+ # node = Prism.parse('"foo"').value.statements.body.first
37
+ # rewritten_source(node, 'to_single_quote') # "'foo'"
38
+ #
39
+ # # to_double_quote for StringNode
40
+ # node = Prism.parse("'foo'").value.statements.body.first
41
+ # rewritten_source(node, 'to_double_quote') # '"foo"'
42
+ #
43
+ # # to_symbol for StringNode
44
+ # node = Prism.parse("'foo'").value.statements.body.first
45
+ # rewritten_source(node, 'to_symbol') # ':foo'
46
+ #
47
+ # # to_string for SymbolNode
48
+ # node = Prism.parse(":foo").value.statements.body.first
49
+ # rewritten_source(node, 'to_string') # 'foo'
50
+ #
51
+ # # to_lambda_literal for CallNode with lambda
52
+ # node = Prism.parse('lambda { foobar }').value.statements.body.first
53
+ # rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'
54
+ #
55
+ # # strip_curly_braces for HashNode
56
+ # node = Prism.parse("{ foo: 'bar' }").value.statements.body.first
57
+ # rewritten_source(node, 'strip_curly_braces') # "foo: 'bar'"
58
+ #
59
+ # # wrap_curly_braces for KeywordHashNode
60
+ # node = Prism.parse("test(foo: 'bar')").value.statements.body.first
61
+ # rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') # "{ foo: 'bar' }"
62
+ def rewritten_source(node, code)
63
+ code.gsub(/{{(.+?)}}/m) do
64
+ old_code = Regexp.last_match(1)
65
+ evaluated = child_node_by_name(node, old_code)
66
+ case evaluated
67
+ when Prism::Node
68
+ get_source(evaluated)
69
+ when Array
70
+ if evaluated.size > 0
71
+ source = get_source(evaluated)
72
+ lines = source.split "\n"
73
+ lines_count = lines.length
74
+ if lines_count > 1 && lines_count == evaluated.size
75
+ new_code = []
76
+ lines.each_with_index { |line, index|
77
+ new_code << (index == 0 ? line : line[get_indent(evaluated.first) - NodeMutation.tab_width..-1])
78
+ }
79
+ new_code.join("\n")
80
+ else
81
+ source
82
+ end
83
+ end
84
+ when String, Symbol, Integer, Float
85
+ evaluated
86
+ when NilClass
87
+ ''
88
+ else
89
+ raise "can not parse \"#{code}\""
90
+ end
91
+ end
92
+ end
93
+
94
+ def file_source(node)
95
+ node.source
96
+ end
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 = Prism.parse('foo.bar(test)').value.statements.body.first
104
+ # child_node_range(node, 'receiver') # { start: 0, end: 'foo'.length }
105
+ #
106
+ # # node array
107
+ # node = Prism.parse('foo.bar(a, b)').value.statements.body.first
108
+ # child_node_range(node, 'arguments.arguments') # { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }
109
+ #
110
+ # # index for node array
111
+ # node = Prism.parse('foo.bar(a, b)').value.statements.body.first
112
+ # child_node_range(node, 'arguments.arguments.parts.-1') # { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }
113
+ #
114
+ # # operator of Binary node
115
+ # node = Prism.parse('foo | bar').value.statements.body.first
116
+ # child_node_range(node, 'operator') # { start: 'foo '.length, end: 'foo |'.length }
117
+ def child_node_range(node, child_name)
118
+ direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
119
+
120
+ if node.is_a?(Array)
121
+ if direct_child_name =~ INDEX_REGEXP
122
+ child_node = node[direct_child_name.to_i]
123
+ raise NodeMutation::MethodNotSupported,
124
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless child_node
125
+ return child_node_range(child_node, nested_child_name) if nested_child_name
126
+
127
+ return NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
128
+ end
129
+
130
+ raise NodeMutation::MethodNotSupported,
131
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
132
+
133
+ child_node = node.send(direct_child_name)
134
+ return child_node_range(child_node, nested_child_name) if nested_child_name
135
+
136
+ return NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
137
+ end
138
+
139
+ if node.respond_to?("#{child_name}_loc")
140
+ node_loc = node.send("#{child_name}_loc")
141
+ NodeMutation::Struct::Range.new(node_loc.start_offset, node_loc.end_offset) if node_loc
142
+ elsif node.is_a?(Prism::CallNode) && child_name.to_sym == :name
143
+ NodeMutation::Struct::Range.new(node.message_loc.start_offset, node.message_loc.end_offset)
144
+ else
145
+ raise NodeMutation::MethodNotSupported,
146
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
147
+
148
+ child_node = node.send(direct_child_name)
149
+
150
+ return child_node_range(child_node, nested_child_name) if nested_child_name
151
+
152
+ return nil if child_node.nil?
153
+ return nil if child_node == []
154
+
155
+ if child_node.is_a?(Prism::Node)
156
+ return(
157
+ NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
158
+ )
159
+ end
160
+
161
+ return(
162
+ NodeMutation::Struct::Range.new(child_node.first.location.start_offset, child_node.last.location.end_offset)
163
+ )
164
+ end
165
+ end
166
+
167
+ def get_start(node, child_name = nil)
168
+ node = child_node_by_name(node, child_name) if child_name
169
+ node.location.start_offset
170
+ end
171
+
172
+ def get_end(node, child_name = nil)
173
+ node = child_node_by_name(node, child_name) if child_name
174
+ node.location.end_offset
175
+ end
176
+
177
+ def get_start_loc(node, child_name = nil)
178
+ node = child_node_by_name(node, child_name) if child_name
179
+ NodeMutation::Struct::Location.new(node.location.start_line, node.location.start_column)
180
+ end
181
+
182
+ def get_end_loc(node, child_name = nil)
183
+ node = child_node_by_name(node, child_name) if child_name
184
+ NodeMutation::Struct::Location.new(node.location.end_line, node.location.end_column)
185
+ end
186
+
187
+ def get_indent(node)
188
+ node.location.start_column
189
+ end
190
+
191
+ private
192
+
193
+ def child_node_by_name(node, child_name)
194
+ direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
195
+
196
+ if node.is_a?(Array)
197
+ if direct_child_name =~ INDEX_REGEXP
198
+ child_node = node[direct_child_name.to_i]
199
+ raise NodeMutation::MethodNotSupported,
200
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless child_node
201
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
202
+
203
+ return child_node
204
+ end
205
+
206
+ raise NodeMutation::MethodNotSupported,
207
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
208
+
209
+ child_node = node.send(direct_child_name)
210
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
211
+
212
+ return child_node
213
+ end
214
+
215
+ if node.respond_to?(direct_child_name)
216
+ child_node = node.send(direct_child_name)
217
+ elsif direct_child_name == 'to_symbol' && node.is_a?(Prism::StringNode)
218
+ child_node = ":#{node.to_value}"
219
+ elsif direct_child_name == 'to_string' && node.is_a?(Prism::SymbolNode)
220
+ child_node = node.to_value.to_s
221
+ elsif direct_child_name == 'to_single_quote' && node.is_a?(Prism::StringNode)
222
+ child_node = "'#{node.to_value}'"
223
+ elsif direct_child_name == 'to_double_quote' && node.is_a?(Prism::StringNode)
224
+ child_node = "\"#{node.to_value}\""
225
+ elsif direct_child_name == 'to_lambda_literal' && node.is_a?(Prism::CallNode) && node.name == :lambda
226
+ if node.block.parameters
227
+ child_node = "->(#{node.block.parameters.parameters.to_source}) { #{node.block.body.to_source} }"
228
+ else
229
+ child_node = "-> #{node.block.to_source}"
230
+ end
231
+ elsif direct_child_name == 'strip_curly_braces' && node.is_a?(Prism::HashNode)
232
+ child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
233
+ elsif direct_child_name == 'wrap_curly_braces' && node.is_a?(Prism::KeywordHashNode)
234
+ child_node = "{ #{node.to_source} }"
235
+ else
236
+ raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
237
+ end
238
+
239
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
240
+
241
+ child_node
242
+ end
243
+ end
@@ -17,47 +17,47 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
17
17
  # @param code [String] The code to evaluate.
18
18
  # @return [String] The new source code.
19
19
  # @example
20
- # node = SyntaxTree::Parser.new('class Synvert; end').parse.statements.body.first
20
+ # node = SyntaxTree.parse('class Synvert; end').statements.body.first
21
21
  # rewritten_source(node, '{{constant}}') # 'Synvert'
22
22
  #
23
23
  # # index for node array
24
- # node = SyntaxTree::Parser.new("foo.bar(a, b)").parse.statements.body.first
24
+ # node = SyntaxTree.parse("foo.bar(a, b)").statements.body.first
25
25
  # rewritten_source(node, '{{arguments.arguments.parts.-1}}')) # 'b'
26
26
  #
27
27
  # # {key}_assoc for HashLiteral node
28
- # node = SyntaxTree::Parser.new("after_commit :do_index, on: :create, if: :indexable?").parse.statements.body.first
28
+ # node = SyntaxTree.parse("after_commit :do_index, on: :create, if: :indexable?").statements.body.first
29
29
  # rewritten_source(node, '{{arguments.parts.-1.on_assoc}}')) # 'on: :create'
30
30
  #
31
31
  # # {key}_value for hash node
32
- # node = SyntaxTree::Parser.new("after_commit :do_index, on: :create, if: :indexable?").parse.statements.body.first
32
+ # node = SyntaxTree.parse("after_commit :do_index, on: :create, if: :indexable?").statements.body.first
33
33
  # rewritten_source(node, '{{arguments.parts.-1.on_value}}')) # ':create'
34
34
  #
35
35
  # # to_single_quote for StringLiteral node
36
- # node = SyntaxTree::Parser.new('"foo"').parse.statements.body.first
36
+ # node = SyntaxTree.parse('"foo"').statements.body.first
37
37
  # rewritten_source(node, 'to_single_quote') # "'foo'"
38
38
  #
39
39
  # # to_double_quote for StringLiteral node
40
- # node = SyntaxTree::Parser.new("'foo'").parse.statements.body.first
40
+ # node = SyntaxTree.parse("'foo'").statements.body.first
41
41
  # rewritten_source(node, 'to_double_quote') # '"foo"'
42
42
  #
43
43
  # # to_symbol for StringLiteral node
44
- # node = SyntaxTree::Parser.new("'foo'").parse.statements.body.first
44
+ # node = SyntaxTree.parse("'foo'").statements.body.first
45
45
  # rewritten_source(node, 'to_symbol') # ':foo'
46
46
  #
47
47
  # # to_string for SymbolLiteral node
48
- # node = SyntaxTree::Parser.new(":foo").parse.statements.body.first
48
+ # node = SyntaxTree.parse(":foo").statements.body.first
49
49
  # rewritten_source(node, 'to_string') # 'foo'
50
50
  #
51
51
  # # to_lambda_literal for MethodAddBlock node
52
- # node = SyntaxTree::Parser.new('lambda { foobar }').parse.statements.body.first
52
+ # node = SyntaxTree.parse('lambda { foobar }').statements.body.first
53
53
  # rewritten_source(node, 'to_lambda_literal') # '-> { foobar }'
54
54
  #
55
55
  # # strip_curly_braces for HashLiteral node
56
- # node = SyntaxTree::Parser.new("{ foo: 'bar' }").parse.statements.body.first
56
+ # node = SyntaxTree.parse("{ foo: 'bar' }").statements.body.first
57
57
  # rewritten_source(node, 'strip_curly_braces') # "foo: 'bar'"
58
58
  #
59
59
  # # wrap_curly_braces for BareAssocHash node
60
- # node = SyntaxTree::Parser.new("test(foo: 'bar')").parse.statements.body.first
60
+ # node = SyntaxTree.parse("test(foo: 'bar')").statements.body.first
61
61
  # rewritten_source(node.arguments.arguments.parts.first, 'wrap_curly_braces') # "{ foo: 'bar' }"
62
62
  def rewritten_source(node, code)
63
63
  code.gsub(/{{(.+?)}}/m) do
@@ -100,19 +100,19 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
100
100
  # @param child_name [String] THe name to find child node.
101
101
  # @return {NodeMutation::Struct::Range} The range of the child node.
102
102
  # @example
103
- # node = SyntaxTree::Parser.new('foo.bar(test)').parse.statements.body.first
103
+ # node = SyntaxTree.parse('foo.bar(test)').statements.body.first
104
104
  # child_node_range(node, 'receiver') # { start: 0, end: 'foo'.length }
105
105
  #
106
106
  # # node array
107
- # node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
107
+ # node = SyntaxTree.parse('foo.bar(a, b)').statements.body.first
108
108
  # child_node_range(node, 'arguments.arguments') # { start: 'foo.bar('.length, end: 'foo.bar(a, b'.length }
109
109
  #
110
110
  # # index for node array
111
- # node = SyntaxTree::Parser.new('foo.bar(a, b)').parse.statements.body.first
111
+ # node = SyntaxTree.parse('foo.bar(a, b)').statements.body.first
112
112
  # child_node_range(node, 'arguments.arguments.parts.-1') # { start: 'foo.bar(a, '.length, end: 'foo.bar(a, b'.length }
113
113
  #
114
114
  # # operator of Binary node
115
- # node = SyntaxTree::Parser.new('foo | bar').parse.statements.body.first
115
+ # node = SyntaxTree.parse('foo | bar').statements.body.first
116
116
  # child_node_range(node, 'operator') # { start: 'foo '.length, end: 'foo |'.length }
117
117
  def child_node_range(node, child_name)
118
118
  direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
@@ -152,6 +152,7 @@ class NodeMutation::SyntaxTreeAdapter < NodeMutation::Adapter
152
152
  return child_node_range(child_node, nested_child_name) if nested_child_name
153
153
 
154
154
  return nil if child_node.nil?
155
+ return nil if child_node == []
155
156
 
156
157
  if child_node.is_a?(SyntaxTree::Node)
157
158
  return(
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class NodeMutation
4
- VERSION = "1.22.4"
4
+ VERSION = "1.23.1"
5
5
  end
data/lib/node_mutation.rb CHANGED
@@ -9,6 +9,7 @@ class NodeMutation
9
9
 
10
10
  autoload :Adapter, "node_mutation/adapter"
11
11
  autoload :ParserAdapter, "node_mutation/adapter/parser"
12
+ autoload :PrismAdapter, "node_mutation/adapter/prism"
12
13
  autoload :SyntaxTreeAdapter, "node_mutation/adapter/syntax_tree"
13
14
  autoload :Action, 'node_mutation/action'
14
15
  autoload :AppendAction, 'node_mutation/action/append_action'
@@ -423,6 +424,8 @@ class NodeMutation
423
424
  ParserAdapter.new
424
425
  when :syntax_tree
425
426
  SyntaxTreeAdapter.new
427
+ when :prism
428
+ PrismAdapter.new
426
429
  else
427
430
  raise InvalidAdapterError, "adapter #{adapter} is not supported"
428
431
  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.22.4
4
+ version: 1.23.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: 2024-01-30 00:00:00.000000000 Z
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: ast node mutation apis
14
14
  email:
@@ -39,6 +39,7 @@ files:
39
39
  - lib/node_mutation/action/replace_with_action.rb
40
40
  - lib/node_mutation/adapter.rb
41
41
  - lib/node_mutation/adapter/parser.rb
42
+ - lib/node_mutation/adapter/prism.rb
42
43
  - lib/node_mutation/adapter/syntax_tree.rb
43
44
  - lib/node_mutation/helper.rb
44
45
  - lib/node_mutation/result.rb