node_mutation 1.22.4 → 1.23.1

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