node_mutation 1.22.4 → 1.23.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: 9485dffc945cb3b645d4abecc7f878550685459ccd2007845471651d6893e2f1
4
- data.tar.gz: 0ab63429232abd2f357b2c791bb3e5bc66a4762ff4175d6444b8c51dbac99a5d
3
+ metadata.gz: 667309a875501329f61441ddc89143500b02d87e83be0a6aa324e78438cfd397
4
+ data.tar.gz: 565be945f6bbfd0976dafb86bb346be696665bd32a72227cf9a4a2d16e75fa45
5
5
  SHA512:
6
- metadata.gz: fc326b1af7d3f13bdd91f24c59d3be4991c20cc751de9bf0b8d48be98dc44fa78c93b4a5ffc75eac2535437ad478a206d54342195737989c17d7eb4121624c8a
7
- data.tar.gz: 92cea72b3327755abb2b789cd704809b46258c3a6cc7f7e9f8bac678e2907e93d7fe59e5abfd33308029bc71dee24a1d8e23cbf976f2a1d7d9648876d6e24b78
6
+ metadata.gz: ed08dbed41c4aa8dce675995a19736ce84877fc0334ab448c04019c7e413204ee2e0c6674ea2d779048962bccbf970990d0b72f89fb7bc17845cb0034b6400ce
7
+ data.tar.gz: 8488422fbcabd15c24c607c1a2b8229bf3a7be810a2d34f0568ceb79a82cc87ebdd06625ab0d76cf68099fd894a067267fc0dde257f5999aa48a8f0ef1c40346
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # NodeMutation
2
2
 
3
+ ## 1.23.0 (2024-02-11)
4
+
5
+ * Support `prism`
6
+
3
7
  ## 1.22.4 (2024-01-30)
4
8
 
5
9
  * 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.0)
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,242 @@
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
+ if node_loc
142
+ NodeMutation::Struct::Range.new(node_loc.start_offset, node_loc.end_offset)
143
+ end
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
+
154
+ if child_node.is_a?(Prism::Node)
155
+ return(
156
+ NodeMutation::Struct::Range.new(child_node.location.start_offset, child_node.location.end_offset)
157
+ )
158
+ end
159
+
160
+ return(
161
+ NodeMutation::Struct::Range.new(child_node.first.location.start_offset, child_node.last.location.end_offset)
162
+ )
163
+ end
164
+ end
165
+
166
+ def get_start(node, child_name = nil)
167
+ node = child_node_by_name(node, child_name) if child_name
168
+ node.location.start_offset
169
+ end
170
+
171
+ def get_end(node, child_name = nil)
172
+ node = child_node_by_name(node, child_name) if child_name
173
+ node.location.end_offset
174
+ end
175
+
176
+ def get_start_loc(node, child_name = nil)
177
+ node = child_node_by_name(node, child_name) if child_name
178
+ NodeMutation::Struct::Location.new(node.location.start_line, node.location.start_column)
179
+ end
180
+
181
+ def get_end_loc(node, child_name = nil)
182
+ node = child_node_by_name(node, child_name) if child_name
183
+ NodeMutation::Struct::Location.new(node.location.end_line, node.location.end_column)
184
+ end
185
+
186
+ def get_indent(node)
187
+ node.location.start_column
188
+ end
189
+
190
+ private
191
+
192
+ def child_node_by_name(node, child_name)
193
+ direct_child_name, nested_child_name = child_name.to_s.split('.', 2)
194
+
195
+ if node.is_a?(Array)
196
+ if direct_child_name =~ INDEX_REGEXP
197
+ child_node = node[direct_child_name.to_i]
198
+ raise NodeMutation::MethodNotSupported,
199
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless child_node
200
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
201
+
202
+ return child_node
203
+ end
204
+
205
+ raise NodeMutation::MethodNotSupported,
206
+ "#{direct_child_name} is not supported for #{get_source(node)}" unless node.respond_to?(direct_child_name)
207
+
208
+ child_node = node.send(direct_child_name)
209
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
210
+
211
+ return child_node
212
+ end
213
+
214
+ if node.respond_to?(direct_child_name)
215
+ child_node = node.send(direct_child_name)
216
+ elsif direct_child_name == 'to_symbol' && node.is_a?(Prism::StringNode)
217
+ child_node = ":#{node.to_value}"
218
+ elsif direct_child_name == 'to_string' && node.is_a?(Prism::SymbolNode)
219
+ child_node = node.to_value.to_s
220
+ elsif direct_child_name == 'to_single_quote' && node.is_a?(Prism::StringNode)
221
+ child_node = "'#{node.to_value}'"
222
+ elsif direct_child_name == 'to_double_quote' && node.is_a?(Prism::StringNode)
223
+ child_node = "\"#{node.to_value}\""
224
+ elsif direct_child_name == 'to_lambda_literal' && node.is_a?(Prism::CallNode) && node.name == :lambda
225
+ if node.block.parameters
226
+ child_node = "->(#{node.block.parameters.parameters.to_source}) { #{node.block.body.to_source} }"
227
+ else
228
+ child_node = "-> #{node.block.to_source}"
229
+ end
230
+ elsif direct_child_name == 'strip_curly_braces' && node.is_a?(Prism::HashNode)
231
+ child_node = node.to_source.sub(/^{(.*)}$/) { Regexp.last_match(1).strip }
232
+ elsif direct_child_name == 'wrap_curly_braces' && node.is_a?(Prism::KeywordHashNode)
233
+ child_node = "{ #{node.to_source} }"
234
+ else
235
+ raise NodeMutation::MethodNotSupported, "#{direct_child_name} is not supported for #{get_source(node)}"
236
+ end
237
+
238
+ return child_node_by_name(child_node, nested_child_name) if nested_child_name
239
+
240
+ child_node
241
+ end
242
+ 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)
@@ -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.0"
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'
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.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: 2024-01-30 00:00:00.000000000 Z
11
+ date: 2024-02-11 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