synvert-core 1.5.0 → 1.6.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 +4 -4
- data/.github/workflows/main.yml +0 -1
- data/.gitignore +0 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +101 -0
- data/README.md +1 -1
- data/lib/synvert/core/node_ext.rb +0 -173
- data/lib/synvert/core/rewriter/condition/if_exist_condition.rb +1 -5
- data/lib/synvert/core/rewriter/condition/if_only_exist_condition.rb +1 -1
- data/lib/synvert/core/rewriter/condition/unless_exist_condition.rb +1 -5
- data/lib/synvert/core/rewriter/condition.rb +5 -1
- data/lib/synvert/core/rewriter/instance.rb +8 -7
- data/lib/synvert/core/rewriter/scope/query_scope.rb +8 -6
- data/lib/synvert/core/rewriter/scope/within_scope.rb +4 -87
- data/lib/synvert/core/version.rb +1 -1
- data/spec/synvert/core/node_ext_spec.rb +0 -170
- data/spec/synvert/core/rewriter/instance_spec.rb +17 -16
- data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +0 -12
- data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +21 -9
- data/synvert-core-ruby.gemspec +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d838da042cfdbc68e4a235117a465ca340fa732203f7470684a9279e6797fff
|
4
|
+
data.tar.gz: ad784c3e09faa73a55e0bf0ad51a982230d9a1b2a328c43414e4aa297e38a00e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b31e94f8071af04f1ae7b1292bb5af91e52ba047949535667cb6765a5ef32c9bbcf855dd92a212aa640b0185f42fbd1bbcf4614f424c596f9c52f62a313a643
|
7
|
+
data.tar.gz: 197318ccffd17c9eb60251731bf49f45198e2b5fb5812c22d4fe6d0d07bac94bff638bc5a125d05be2ccec930b16300ccdb83ffdd53bf8cf0c4cd8ad22a34c94
|
data/.github/workflows/main.yml
CHANGED
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
synvert-core (1.6.0)
|
5
|
+
activesupport (< 7.0.0)
|
6
|
+
erubis
|
7
|
+
node_mutation
|
8
|
+
node_query
|
9
|
+
parser
|
10
|
+
parser_node_ext
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: https://rubygems.org/
|
14
|
+
specs:
|
15
|
+
activesupport (6.1.7)
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
tzinfo (~> 2.0)
|
20
|
+
zeitwerk (~> 2.3)
|
21
|
+
ast (2.4.2)
|
22
|
+
coderay (1.1.3)
|
23
|
+
concurrent-ruby (1.1.10)
|
24
|
+
diff-lcs (1.5.0)
|
25
|
+
erubis (2.7.0)
|
26
|
+
ffi (1.15.5)
|
27
|
+
formatador (1.1.0)
|
28
|
+
guard (2.18.0)
|
29
|
+
formatador (>= 0.2.4)
|
30
|
+
listen (>= 2.7, < 4.0)
|
31
|
+
lumberjack (>= 1.0.12, < 2.0)
|
32
|
+
nenv (~> 0.1)
|
33
|
+
notiffany (~> 0.0)
|
34
|
+
pry (>= 0.13.0)
|
35
|
+
shellany (~> 0.0)
|
36
|
+
thor (>= 0.18.1)
|
37
|
+
guard-compat (1.2.1)
|
38
|
+
guard-rspec (4.7.3)
|
39
|
+
guard (~> 2.1)
|
40
|
+
guard-compat (~> 1.1)
|
41
|
+
rspec (>= 2.99.0, < 4.0)
|
42
|
+
i18n (1.12.0)
|
43
|
+
concurrent-ruby (~> 1.0)
|
44
|
+
listen (3.7.1)
|
45
|
+
rb-fsevent (~> 0.10, >= 0.10.3)
|
46
|
+
rb-inotify (~> 0.9, >= 0.9.10)
|
47
|
+
lumberjack (1.2.8)
|
48
|
+
method_source (1.0.0)
|
49
|
+
minitest (5.16.3)
|
50
|
+
nenv (0.3.0)
|
51
|
+
node_mutation (1.2.1)
|
52
|
+
activesupport
|
53
|
+
erubis
|
54
|
+
node_query (1.5.0)
|
55
|
+
activesupport (< 7.0.0)
|
56
|
+
notiffany (0.1.3)
|
57
|
+
nenv (~> 0.1)
|
58
|
+
shellany (~> 0.0)
|
59
|
+
parser (3.1.2.1)
|
60
|
+
ast (~> 2.4.1)
|
61
|
+
parser_node_ext (0.4.0)
|
62
|
+
parser
|
63
|
+
pry (0.14.1)
|
64
|
+
coderay (~> 1.1)
|
65
|
+
method_source (~> 1.0)
|
66
|
+
rake (13.0.6)
|
67
|
+
rb-fsevent (0.11.1)
|
68
|
+
rb-inotify (0.10.1)
|
69
|
+
ffi (~> 1.0)
|
70
|
+
rspec (3.10.0)
|
71
|
+
rspec-core (~> 3.10.0)
|
72
|
+
rspec-expectations (~> 3.10.0)
|
73
|
+
rspec-mocks (~> 3.10.0)
|
74
|
+
rspec-core (3.10.2)
|
75
|
+
rspec-support (~> 3.10.0)
|
76
|
+
rspec-expectations (3.10.2)
|
77
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
78
|
+
rspec-support (~> 3.10.0)
|
79
|
+
rspec-mocks (3.10.2)
|
80
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
81
|
+
rspec-support (~> 3.10.0)
|
82
|
+
rspec-support (3.10.3)
|
83
|
+
shellany (0.0.1)
|
84
|
+
thor (1.2.1)
|
85
|
+
tzinfo (2.0.5)
|
86
|
+
concurrent-ruby (~> 1.0)
|
87
|
+
zeitwerk (2.6.0)
|
88
|
+
|
89
|
+
PLATFORMS
|
90
|
+
ruby
|
91
|
+
|
92
|
+
DEPENDENCIES
|
93
|
+
guard
|
94
|
+
guard-rspec
|
95
|
+
rake
|
96
|
+
rspec
|
97
|
+
rspec-mocks
|
98
|
+
synvert-core!
|
99
|
+
|
100
|
+
BUNDLED WITH
|
101
|
+
2.3.7
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# synvert-core-ruby
|
2
2
|
|
3
|
-
<img src="https://synvert.
|
3
|
+
<img src="https://synvert.net/img/logo_96.png" alt="logo" width="32" height="32" />
|
4
4
|
|
5
5
|
[](https://awesomecode.io/repos/xinminlabs/synvert-core-ruby)
|
6
6
|

|
@@ -50,43 +50,6 @@ module Parser::AST
|
|
50
50
|
loc.expression.line
|
51
51
|
end
|
52
52
|
|
53
|
-
# Match node with rules.
|
54
|
-
# It provides some additional keywords to match rules, +any+, +contain+, +not+, +in+, +not_in+, +gt+, +gte+, +lt+, +lte+.
|
55
|
-
# @example
|
56
|
-
# type: 'send', arguments: { any: 'Lifo::ShowExceptions' }
|
57
|
-
# type: { in: ['send', 'csend'] }
|
58
|
-
# type: :send, arguments: { length: { gt: 2 } }
|
59
|
-
# @param rules [Hash] rules to match.
|
60
|
-
# @return true if matches.
|
61
|
-
def match?(rules)
|
62
|
-
keywords = %i[any contain not in not_in gt gte lt lte]
|
63
|
-
flat_hash(rules).keys.all? do |multi_keys|
|
64
|
-
last_key = multi_keys.last
|
65
|
-
actual = keywords.include?(last_key) ? actual_value(multi_keys[0...-1]) : actual_value(multi_keys)
|
66
|
-
expected = expected_value(rules, multi_keys)
|
67
|
-
case last_key
|
68
|
-
when :any, :contain
|
69
|
-
actual.any? { |actual_value| match_value?(actual_value, expected) }
|
70
|
-
when :not
|
71
|
-
!match_value?(actual, expected)
|
72
|
-
when :in
|
73
|
-
expected.any? { |expected_value| match_value?(actual, expected_value) }
|
74
|
-
when :not_in
|
75
|
-
expected.all? { |expected_value| !match_value?(actual, expected_value) }
|
76
|
-
when :gt
|
77
|
-
actual > expected
|
78
|
-
when :gte
|
79
|
-
actual >= expected
|
80
|
-
when :lt
|
81
|
-
actual < expected
|
82
|
-
when :lte
|
83
|
-
actual <= expected
|
84
|
-
else
|
85
|
-
match_value?(actual, expected)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
53
|
# Strip curly braces for hash.
|
91
54
|
# @example
|
92
55
|
# node # s(:hash, s(:pair, s(:sym, :foo), s(:str, "bar")))
|
@@ -161,141 +124,5 @@ module Parser::AST
|
|
161
124
|
to_source
|
162
125
|
end
|
163
126
|
end
|
164
|
-
|
165
|
-
# Convert node to a hash, so that it can be converted to a json.
|
166
|
-
def to_hash
|
167
|
-
result = { type: type }
|
168
|
-
if TYPE_CHILDREN[type]
|
169
|
-
TYPE_CHILDREN[type].each do |key|
|
170
|
-
value = send(key)
|
171
|
-
result[key] =
|
172
|
-
case value
|
173
|
-
when Array
|
174
|
-
value.map { |v| v.respond_to?(:to_hash) ? v.to_hash : v }
|
175
|
-
when Parser::AST::Node
|
176
|
-
value.to_hash
|
177
|
-
else
|
178
|
-
value
|
179
|
-
end
|
180
|
-
end
|
181
|
-
else
|
182
|
-
result[:children] = children.map { |c| c.respond_to?(:to_hash) ? c.to_hash : c }
|
183
|
-
end
|
184
|
-
result
|
185
|
-
end
|
186
|
-
|
187
|
-
private
|
188
|
-
|
189
|
-
# Compare actual value with expected value.
|
190
|
-
#
|
191
|
-
# @param actual [Object] actual value.
|
192
|
-
# @param expected [Object] expected value.
|
193
|
-
# @return [Boolean]
|
194
|
-
# @raise [Synvert::Core::MethodNotSupported] if expected class is not supported.
|
195
|
-
def match_value?(actual, expected)
|
196
|
-
return true if actual == expected
|
197
|
-
|
198
|
-
case expected
|
199
|
-
when Symbol
|
200
|
-
if actual.is_a?(Parser::AST::Node)
|
201
|
-
actual.to_source == ":#{expected}" || actual.to_source == expected.to_s
|
202
|
-
else
|
203
|
-
actual.to_sym == expected
|
204
|
-
end
|
205
|
-
when String
|
206
|
-
if actual.is_a?(Parser::AST::Node)
|
207
|
-
actual.to_source == expected || actual.to_source == unwrap_quote(expected) ||
|
208
|
-
unwrap_quote(actual.to_source) == expected || unwrap_quote(actual.to_source) == unwrap_quote(expected)
|
209
|
-
else
|
210
|
-
actual.to_s == expected || wrap_quote(actual.to_s) == expected
|
211
|
-
end
|
212
|
-
when Regexp
|
213
|
-
if actual.is_a?(Parser::AST::Node)
|
214
|
-
actual.to_source =~ Regexp.new(expected.to_s, Regexp::MULTILINE)
|
215
|
-
else
|
216
|
-
actual.to_s =~ Regexp.new(expected.to_s, Regexp::MULTILINE)
|
217
|
-
end
|
218
|
-
when Array
|
219
|
-
return false unless expected.length == actual.length
|
220
|
-
|
221
|
-
actual.zip(expected).all? { |a, e| match_value?(a, e) }
|
222
|
-
when NilClass
|
223
|
-
if actual.is_a?(Parser::AST::Node)
|
224
|
-
:nil == actual.type
|
225
|
-
else
|
226
|
-
actual.nil?
|
227
|
-
end
|
228
|
-
when Numeric
|
229
|
-
if actual.is_a?(Parser::AST::Node)
|
230
|
-
actual.children[0] == expected
|
231
|
-
else
|
232
|
-
actual == expected
|
233
|
-
end
|
234
|
-
when TrueClass
|
235
|
-
:true == actual.type
|
236
|
-
when FalseClass
|
237
|
-
:false == actual.type
|
238
|
-
when Parser::AST::Node
|
239
|
-
actual == expected
|
240
|
-
when Synvert::Core::Rewriter::AnyValue
|
241
|
-
!actual.nil?
|
242
|
-
else
|
243
|
-
raise Synvert::Core::MethodNotSupported, "#{expected.class} is not handled for match_value?"
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# Convert a hash to flat one.
|
248
|
-
#
|
249
|
-
# @example
|
250
|
-
# flat_hash(type: 'block', caller: {type: 'send', receiver: 'RSpec'})
|
251
|
-
# # {[:type] => 'block', [:caller, :type] => 'send', [:caller, :receiver] => 'RSpec'}
|
252
|
-
# @param h [Hash] original hash.
|
253
|
-
# @return flatten hash.
|
254
|
-
def flat_hash(h, k = [])
|
255
|
-
new_hash = {}
|
256
|
-
h.each_pair do |key, val|
|
257
|
-
if val.is_a?(Hash)
|
258
|
-
new_hash.merge!(flat_hash(val, k + [key]))
|
259
|
-
else
|
260
|
-
new_hash[k + [key]] = val
|
261
|
-
end
|
262
|
-
end
|
263
|
-
new_hash
|
264
|
-
end
|
265
|
-
|
266
|
-
# Get actual value from the node.
|
267
|
-
#
|
268
|
-
# @param multi_keys [Array<Symbol, String>]
|
269
|
-
# @return [Object] actual value.
|
270
|
-
def actual_value(multi_keys)
|
271
|
-
multi_keys.inject(self) { |n, key| n.send(key) if n }
|
272
|
-
end
|
273
|
-
|
274
|
-
# Get expected value from rules.
|
275
|
-
#
|
276
|
-
# @param rules [Hash]
|
277
|
-
# @param multi_keys [Array<Symbol>]
|
278
|
-
# @return [Object] expected value.
|
279
|
-
def expected_value(rules, multi_keys)
|
280
|
-
multi_keys.inject(rules) { |o, key| o[key] }
|
281
|
-
end
|
282
|
-
|
283
|
-
# Wrap the string with single or double quote.
|
284
|
-
def wrap_quote(string)
|
285
|
-
if string.include?("'")
|
286
|
-
"\"#{string}\""
|
287
|
-
else
|
288
|
-
"'#{string}'"
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
# Unwrap the quote from the string.
|
293
|
-
def unwrap_quote(string)
|
294
|
-
if (string[0] == '"' && string[-1] == '"') || (string[0] == "'" && string[-1] == "'")
|
295
|
-
string[1...-1]
|
296
|
-
else
|
297
|
-
string
|
298
|
-
end
|
299
|
-
end
|
300
127
|
end
|
301
128
|
end
|
@@ -9,11 +9,7 @@ module Synvert::Core
|
|
9
9
|
#
|
10
10
|
# @return [Boolean]
|
11
11
|
def match?
|
12
|
-
|
13
|
-
NodeQuery::Helper.handle_recursive_child(@instance.current_node) do |child_node|
|
14
|
-
match ||= child_node&.match?(@rules)
|
15
|
-
end
|
16
|
-
match
|
12
|
+
@node_query.query_nodes(target_node, including_self: false, stop_at_first_match: true).size > 0
|
17
13
|
end
|
18
14
|
end
|
19
15
|
end
|
@@ -9,11 +9,7 @@ module Synvert::Core
|
|
9
9
|
#
|
10
10
|
# return [Boolean]
|
11
11
|
def match?
|
12
|
-
|
13
|
-
NodeQuery::Helper.handle_recursive_child(@instance.current_node) do |child_node|
|
14
|
-
match ||= child_node&.match?(@rules)
|
15
|
-
end
|
16
|
-
!match
|
12
|
+
@node_query.query_nodes(target_node, including_self: false, stop_at_first_match: true).size == 0
|
17
13
|
end
|
18
14
|
end
|
19
15
|
end
|
@@ -10,7 +10,7 @@ module Synvert::Core
|
|
10
10
|
# @yield run when condition matches
|
11
11
|
def initialize(instance, rules, &block)
|
12
12
|
@instance = instance
|
13
|
-
@
|
13
|
+
@node_query = NodeQuery.new(rules)
|
14
14
|
@block = block
|
15
15
|
end
|
16
16
|
|
@@ -27,5 +27,9 @@ module Synvert::Core
|
|
27
27
|
def match?
|
28
28
|
raise NotImplementedError, 'must be implemented by subclasses'
|
29
29
|
end
|
30
|
+
|
31
|
+
def target_node
|
32
|
+
@instance.current_node
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
@@ -79,11 +79,13 @@ module Synvert::Core
|
|
79
79
|
# # matches FactoryBot.create(:user)
|
80
80
|
# find_node '.send[receiver=FactoryBot][message=create][arguments.size=1]' do
|
81
81
|
# end
|
82
|
-
# @param
|
82
|
+
# @param nql [String] node query language to find matching ast nodes.
|
83
83
|
# @yield run on the matching nodes.
|
84
84
|
# @raise [Synvert::Core::NodeQuery::Compiler::ParseError] if query string is invalid.
|
85
|
-
def find_node(
|
86
|
-
Rewriter::QueryScope.new(self,
|
85
|
+
def find_node(nql, options = {}, &block)
|
86
|
+
Rewriter::QueryScope.new(self, nql, options, &block).process
|
87
|
+
rescue NodeQueryLexer::ScanError, Racc::ParseError => e
|
88
|
+
raise NodeQuery::Compiler::ParseError, "Invalid query string: #{nql}"
|
87
89
|
end
|
88
90
|
|
89
91
|
# Parse +within_node+ dsl, it creates a {Synvert::Core::Rewriter::WithinScope} to recursively find matching ast nodes,
|
@@ -94,12 +96,11 @@ module Synvert::Core
|
|
94
96
|
# end
|
95
97
|
# @param rules [Hash] rules to find mathing ast nodes.
|
96
98
|
# @param options [Hash] optional
|
97
|
-
# @option
|
98
|
-
# @option
|
99
|
+
# @option including_self [Boolean] set if query the current node, default is true
|
100
|
+
# @option stop_at_first_match [Boolean] set if stop at first match, default is false
|
101
|
+
# @option recursive [Boolean] set if recursively query child nodes, default is true
|
99
102
|
# @yield run on the matching nodes.
|
100
103
|
def within_node(rules, options = {}, &block)
|
101
|
-
options[:stop_when_match] ||= false
|
102
|
-
options[:direct] ||= false
|
103
104
|
Rewriter::WithinScope.new(self, rules, options, &block).process
|
104
105
|
end
|
105
106
|
|
@@ -6,11 +6,14 @@ module Synvert::Core
|
|
6
6
|
# Initialize a QueryScope.
|
7
7
|
#
|
8
8
|
# @param instance [Synvert::Core::Rewriter::Instance]
|
9
|
-
# @param
|
9
|
+
# @param nql [String]
|
10
|
+
# @param options [Hash]
|
10
11
|
# @yield run on all matching nodes
|
11
|
-
def initialize(instance,
|
12
|
+
def initialize(instance, nql, options = {}, &block)
|
12
13
|
super(instance, &block)
|
13
|
-
|
14
|
+
|
15
|
+
@options = { including_self: true, stop_at_first_match: false, recursive: true }.merge(options)
|
16
|
+
@node_query = NodeQuery.new(nql)
|
14
17
|
end
|
15
18
|
|
16
19
|
# Find out the matching nodes.
|
@@ -22,15 +25,14 @@ module Synvert::Core
|
|
22
25
|
current_node = @instance.current_node
|
23
26
|
return unless current_node
|
24
27
|
|
28
|
+
matching_nodes = @node_query.query_nodes(current_node, @options)
|
25
29
|
@instance.process_with_node(current_node) do
|
26
|
-
|
30
|
+
matching_nodes.each do |node|
|
27
31
|
@instance.process_with_node(node) do
|
28
32
|
@instance.instance_eval(&@block)
|
29
33
|
end
|
30
34
|
end
|
31
35
|
end
|
32
|
-
rescue NodeQueryLexer::ScanError, Racc::ParseError => e
|
33
|
-
raise NodeQuery::Compiler::ParseError, "Invalid query string: #{@query_string}"
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
@@ -11,8 +11,9 @@ module Synvert::Core
|
|
11
11
|
# @yield run on all matching nodes
|
12
12
|
def initialize(instance, rules, options = {}, &block)
|
13
13
|
super(instance, &block)
|
14
|
-
|
15
|
-
@options = options
|
14
|
+
|
15
|
+
@options = { including_self: true, stop_at_first_match: false, recursive: true }.merge(options)
|
16
|
+
@node_query = NodeQuery.new(rules)
|
16
17
|
end
|
17
18
|
|
18
19
|
# Find out the matching nodes.
|
@@ -22,14 +23,7 @@ module Synvert::Core
|
|
22
23
|
current_node = @instance.current_node
|
23
24
|
return unless current_node
|
24
25
|
|
25
|
-
matching_nodes =
|
26
|
-
if @options[:direct]
|
27
|
-
find_direct_matching_nodes(current_node)
|
28
|
-
elsif @options[:stop_when_match]
|
29
|
-
find_matching_nodes(current_node)
|
30
|
-
else
|
31
|
-
find_recursive_matching_nodes(current_node)
|
32
|
-
end
|
26
|
+
matching_nodes = @node_query.query_nodes(current_node, @options)
|
33
27
|
@instance.process_with_node current_node do
|
34
28
|
matching_nodes.each do |matching_node|
|
35
29
|
@instance.process_with_node matching_node do
|
@@ -38,82 +32,5 @@ module Synvert::Core
|
|
38
32
|
end
|
39
33
|
end
|
40
34
|
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
# Find the matching nodes only in current or direct children.
|
45
|
-
#
|
46
|
-
# @param current_node [Parser::AST::Node]
|
47
|
-
def find_direct_matching_nodes(current_node)
|
48
|
-
matching_nodes = []
|
49
|
-
if current_node.is_a?(Parser::AST::Node)
|
50
|
-
if current_node.type == :begin
|
51
|
-
current_node.children.each do |child_node|
|
52
|
-
matching_nodes << child_node if child_node.match?(@rules)
|
53
|
-
end
|
54
|
-
elsif current_node.match?(@rules)
|
55
|
-
matching_nodes << current_node
|
56
|
-
end
|
57
|
-
else
|
58
|
-
current_node.each do |child_node|
|
59
|
-
matching_nodes << child_node if child_node.match?(@rules)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
matching_nodes
|
63
|
-
end
|
64
|
-
|
65
|
-
# Find matching nodes in all recursive children.
|
66
|
-
#
|
67
|
-
# @param current_node [Parser::AST::Node]
|
68
|
-
def find_recursive_matching_nodes(current_node)
|
69
|
-
matching_nodes = []
|
70
|
-
if current_node.is_a?(Parser::AST::Node)
|
71
|
-
matching_nodes << current_node if current_node.match?(@rules)
|
72
|
-
NodeQuery::Helper.handle_recursive_child(current_node) do |child_node|
|
73
|
-
matching_nodes << child_node if child_node.match?(@rules)
|
74
|
-
end
|
75
|
-
else
|
76
|
-
current_node.each do |node|
|
77
|
-
matching_nodes << node if node.match?(@rules)
|
78
|
-
NodeQuery::Helper.handle_recursive_child(node) do |child_node|
|
79
|
-
matching_nodes << child_node if child_node.match?(@rules)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
matching_nodes
|
84
|
-
end
|
85
|
-
|
86
|
-
# Find matching nodes in recursive children but do not continue on matching nodes.
|
87
|
-
#
|
88
|
-
# @param current_node [Parser::AST::Node]
|
89
|
-
def find_matching_nodes(current_node)
|
90
|
-
matching_nodes = []
|
91
|
-
if current_node.is_a?(Parser::AST::Node)
|
92
|
-
if current_node.match?(@rules)
|
93
|
-
matching_nodes << current_node
|
94
|
-
return matching_nodes
|
95
|
-
end
|
96
|
-
NodeQuery::Helper.handle_recursive_child(current_node) do |child_node|
|
97
|
-
if child_node.match?(@rules)
|
98
|
-
matching_nodes << child_node
|
99
|
-
next :stop
|
100
|
-
end
|
101
|
-
end
|
102
|
-
else
|
103
|
-
current_node.each do |node|
|
104
|
-
if node.match?(@rules)
|
105
|
-
matching_nodes << node
|
106
|
-
next
|
107
|
-
end
|
108
|
-
NodeQuery::Helper.handle_recursive_child(node) do |child_node|
|
109
|
-
if child_node.match?(@rules)
|
110
|
-
matching_nodes << child_node
|
111
|
-
next :stop
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
matching_nodes
|
117
|
-
end
|
118
35
|
end
|
119
36
|
end
|
data/lib/synvert/core/version.rb
CHANGED
@@ -3,133 +3,6 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
describe Parser::AST::Node do
|
6
|
-
describe '#match?' do
|
7
|
-
it 'matches class name' do
|
8
|
-
source = 'class Synvert; end'
|
9
|
-
node = parse(source)
|
10
|
-
expect(node).to be_match(type: 'class', name: 'Synvert')
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'matches message with regexp' do
|
14
|
-
source = 'User.find_by_login(login)'
|
15
|
-
node = parse(source)
|
16
|
-
expect(node).to be_match(type: 'send', message: /^find_by_/)
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'matches arguments with symbol' do
|
20
|
-
source = 'params[:user]'
|
21
|
-
node = parse(source)
|
22
|
-
expect(node).to be_match(type: 'send', receiver: 'params', message: '[]', arguments: [:user])
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'matches pair key with symbol' do
|
26
|
-
source = '{ type: :model }'
|
27
|
-
node = parse(source).children[0]
|
28
|
-
expect(node).to be_match(type: 'pair', key: :type)
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'matches assign number' do
|
32
|
-
source = 'at_least(0)'
|
33
|
-
node = parse(source)
|
34
|
-
expect(node).to be_match(type: 'send', arguments: [0])
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'matches assign float' do
|
38
|
-
source = 'at_least(1.5)'
|
39
|
-
node = parse(source)
|
40
|
-
expect(node).to be_match(type: 'send', arguments: [1.5])
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'matches arguments with string' do
|
44
|
-
source = 'params["user"]'
|
45
|
-
node = parse(source)
|
46
|
-
expect(node).to be_match(type: 'send', receiver: 'params', message: '[]', arguments: ['user'])
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'matches arguments with string 2' do
|
50
|
-
source = 'params["user"]'
|
51
|
-
node = parse(source)
|
52
|
-
expect(node).to be_match(type: 'send', receiver: 'params', message: '[]', arguments: ["'user'"])
|
53
|
-
end
|
54
|
-
|
55
|
-
it 'matches arguments with string 3' do
|
56
|
-
source = "{ notice: 'Welcome' }"
|
57
|
-
node = parse(source)
|
58
|
-
expect(node).to be_match(type: 'hash', notice_value: "'Welcome'")
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'matches arguments any' do
|
62
|
-
source = 'config.middleware.insert_after ActiveRecord::QueryCache, Lifo::Cache, page_cache: false'
|
63
|
-
node = parse(source)
|
64
|
-
expect(node).to be_match(type: 'send', arguments: { any: 'Lifo::Cache' })
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'matches arguments with nested hash' do
|
68
|
-
source = '{ user_id: user.id }'
|
69
|
-
node = parse(source)
|
70
|
-
expect(node).to be_match(
|
71
|
-
type: 'hash',
|
72
|
-
user_id_value: {
|
73
|
-
type: 'send',
|
74
|
-
receiver: { type: 'send', message: 'user' },
|
75
|
-
message: 'id'
|
76
|
-
}
|
77
|
-
)
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'matches arguments contain' do
|
81
|
-
source = 'def slow(foo, bar, &block); end'
|
82
|
-
node = parse(source)
|
83
|
-
expect(node).to be_match(type: 'def', arguments: { contain: '&block' })
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'matches not' do
|
87
|
-
source = 'class Synvert; end'
|
88
|
-
node = parse(source)
|
89
|
-
expect(node).not_to be_match(type: 'class', name: { not: 'Synvert' })
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'matches in' do
|
93
|
-
source = 'FactoryBot.create(:user)'
|
94
|
-
node = parse(source)
|
95
|
-
expect(node).to be_match(type: 'send', message: { in: %i[create build] })
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'matches not_in' do
|
99
|
-
source = 'FactoryBot.create(:user)'
|
100
|
-
node = parse(source)
|
101
|
-
expect(node).not_to be_match(type: 'send', message: { not_in: %i[create build] })
|
102
|
-
end
|
103
|
-
|
104
|
-
it 'matches gt' do
|
105
|
-
source = 'foobar(foo, bar)'
|
106
|
-
node = parse(source)
|
107
|
-
expect(node).to be_match(type: 'send', arguments: { size: { gt: 1 } })
|
108
|
-
expect(node).not_to be_match(type: 'send', arguments: { size: { gt: 2 } })
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'matches gte' do
|
112
|
-
source = 'foobar(foo, bar)'
|
113
|
-
node = parse(source)
|
114
|
-
expect(node).to be_match(type: 'send', arguments: { size: { gte: 2 } })
|
115
|
-
expect(node).not_to be_match(type: 'send', arguments: { size: { gte: 3 } })
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'matches lt' do
|
119
|
-
source = 'foobar(foo, bar)'
|
120
|
-
node = parse(source)
|
121
|
-
expect(node).to be_match(type: 'send', arguments: { size: { lt: 3 } })
|
122
|
-
expect(node).not_to be_match(type: 'send', arguments: { size: { lt: 2 } })
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'matches lte' do
|
126
|
-
source = 'foobar(foo, bar)'
|
127
|
-
node = parse(source)
|
128
|
-
expect(node).to be_match(type: 'send', arguments: { size: { lte: 2 } })
|
129
|
-
expect(node).not_to be_match(type: 'send', arguments: { size: { lte: 1 } })
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
6
|
describe '#strip_curly_braces' do
|
134
7
|
context 'hash node' do
|
135
8
|
it 'removes curly braces' do
|
@@ -232,47 +105,4 @@ describe Parser::AST::Node do
|
|
232
105
|
end
|
233
106
|
end
|
234
107
|
end
|
235
|
-
|
236
|
-
describe '#to_hash' do
|
237
|
-
it 'gets hash' do
|
238
|
-
node = parse(<<~EOS)
|
239
|
-
class Synvert
|
240
|
-
def foobar(foo, bar)
|
241
|
-
{ foo => bar }
|
242
|
-
end
|
243
|
-
end
|
244
|
-
EOS
|
245
|
-
expect(node.to_hash).to eq(
|
246
|
-
{
|
247
|
-
type: :class,
|
248
|
-
parent_class: nil,
|
249
|
-
name: {
|
250
|
-
type: :const,
|
251
|
-
parent_const: nil,
|
252
|
-
name: :Synvert
|
253
|
-
},
|
254
|
-
body: [
|
255
|
-
{
|
256
|
-
type: :def,
|
257
|
-
name: :foobar,
|
258
|
-
arguments: [
|
259
|
-
{ type: :arg, name: :foo },
|
260
|
-
{ type: :arg, name: :bar }
|
261
|
-
],
|
262
|
-
body: [
|
263
|
-
{
|
264
|
-
type: :hash,
|
265
|
-
pairs: {
|
266
|
-
type: :pair,
|
267
|
-
key: { type: :lvar, name: :foo },
|
268
|
-
value: { type: :lvar, name: :bar }
|
269
|
-
}
|
270
|
-
}
|
271
|
-
]
|
272
|
-
}
|
273
|
-
]
|
274
|
-
}
|
275
|
-
)
|
276
|
-
end
|
277
|
-
end
|
278
108
|
end
|
@@ -12,16 +12,27 @@ module Synvert::Core
|
|
12
12
|
it 'parses find_node' do
|
13
13
|
scope = double
|
14
14
|
block = proc {}
|
15
|
-
expect(Rewriter::QueryScope).to receive(:new).with(instance, '.send[message=create]', &block).and_return(scope)
|
15
|
+
expect(Rewriter::QueryScope).to receive(:new).with(instance, '.send[message=create]', {}, &block).and_return(scope)
|
16
16
|
expect(scope).to receive(:process)
|
17
17
|
instance.find_node('.send[message=create]', &block)
|
18
18
|
end
|
19
19
|
|
20
|
+
it 'raises ParseError when parsing invalid nql' do
|
21
|
+
block = proc {}
|
22
|
+
expect {
|
23
|
+
instance.find_node('synvert', &block)
|
24
|
+
}.to raise_error(NodeQuery::Compiler::ParseError)
|
25
|
+
|
26
|
+
expect {
|
27
|
+
instance.find_node('.block <caller.arguments> .hash', &block)
|
28
|
+
}.to raise_error(NodeQuery::Compiler::ParseError)
|
29
|
+
end
|
30
|
+
|
20
31
|
it 'parses within_node' do
|
21
32
|
scope = double
|
22
33
|
block = proc {}
|
23
34
|
expect(Rewriter::WithinScope).to receive(:new)
|
24
|
-
.with(instance, { type: 'send', message: 'create' }, {
|
35
|
+
.with(instance, { type: 'send', message: 'create' }, {}, &block)
|
25
36
|
.and_return(scope)
|
26
37
|
expect(scope).to receive(:process)
|
27
38
|
instance.within_node(type: 'send', message: 'create', &block)
|
@@ -31,30 +42,20 @@ module Synvert::Core
|
|
31
42
|
scope = double
|
32
43
|
block = proc {}
|
33
44
|
expect(Rewriter::WithinScope).to receive(:new)
|
34
|
-
.with(instance, { type: 'send', message: 'create' }, {
|
45
|
+
.with(instance, { type: 'send', message: 'create' }, {}, &block)
|
35
46
|
.and_return(scope)
|
36
47
|
expect(scope).to receive(:process)
|
37
48
|
instance.with_node(type: 'send', message: 'create', &block)
|
38
49
|
end
|
39
50
|
|
40
|
-
it 'parses within_node with
|
41
|
-
scope = double
|
42
|
-
block = proc {}
|
43
|
-
expect(Rewriter::WithinScope).to receive(:new)
|
44
|
-
.with(instance, { type: 'send', message: 'create' }, { stop_when_match: true, direct: false }, &block)
|
45
|
-
.and_return(scope)
|
46
|
-
expect(scope).to receive(:process)
|
47
|
-
instance.within_node({ type: 'send', message: 'create' }, { stop_when_match: true }, &block)
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'parses within_node with direct true' do
|
51
|
+
it 'parses within_node with stop_at_first_match true' do
|
51
52
|
scope = double
|
52
53
|
block = proc {}
|
53
54
|
expect(Rewriter::WithinScope).to receive(:new)
|
54
|
-
.with(instance, { type: 'send', message: 'create' }, {
|
55
|
+
.with(instance, { type: 'send', message: 'create' }, { stop_at_first_match: true }, &block)
|
55
56
|
.and_return(scope)
|
56
57
|
expect(scope).to receive(:process)
|
57
|
-
instance.within_node({ type: 'send', message: 'create' }, {
|
58
|
+
instance.within_node({ type: 'send', message: 'create' }, { stop_at_first_match: true }, &block)
|
58
59
|
end
|
59
60
|
|
60
61
|
it 'parses goto_node' do
|
@@ -55,18 +55,6 @@ module Synvert::Core
|
|
55
55
|
expect(block_nodes.size).to eq 2
|
56
56
|
end
|
57
57
|
|
58
|
-
it 'raises ParseError' do
|
59
|
-
scope = described_class.new(instance, 'synvert') {}
|
60
|
-
expect {
|
61
|
-
scope.process
|
62
|
-
}.to raise_error(NodeQuery::Compiler::ParseError)
|
63
|
-
|
64
|
-
scope = described_class.new(instance, '.block <caller.arguments> .hash') {}
|
65
|
-
expect {
|
66
|
-
scope.process
|
67
|
-
}.to raise_error(NodeQuery::Compiler::ParseError)
|
68
|
-
end
|
69
|
-
|
70
58
|
it 'raises InvalidOperatorError' do
|
71
59
|
scope = described_class.new(instance, '.send[receiver IN FactoryGirl]') {}
|
72
60
|
expect {
|
@@ -9,9 +9,11 @@ module Synvert::Core
|
|
9
9
|
Rewriter::Instance.new(rewriter, 'file pattern')
|
10
10
|
}
|
11
11
|
let(:source) { <<~EOS }
|
12
|
-
describe
|
13
|
-
|
14
|
-
|
12
|
+
describe User do
|
13
|
+
describe 'create' do
|
14
|
+
it 'creates user' do
|
15
|
+
FactoryGirl.create :user
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
EOS
|
@@ -39,7 +41,7 @@ module Synvert::Core
|
|
39
41
|
type: 'send',
|
40
42
|
receiver: 'FactoryGirl',
|
41
43
|
message: 'create',
|
42
|
-
arguments: [':
|
44
|
+
arguments: [':user'] do
|
43
45
|
run = true
|
44
46
|
type_in_scope = node.type
|
45
47
|
end
|
@@ -52,27 +54,37 @@ module Synvert::Core
|
|
52
54
|
it 'matches multiple block nodes' do
|
53
55
|
block_nodes = []
|
54
56
|
scope =
|
55
|
-
Rewriter::WithinScope.new(instance, { type: 'block' }
|
57
|
+
Rewriter::WithinScope.new(instance, { type: 'block' }) do
|
58
|
+
block_nodes << node
|
59
|
+
end
|
60
|
+
scope.process
|
61
|
+
expect(block_nodes.size).to eq 3
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'matches only 2 block nodes if including_self is false' do
|
65
|
+
block_nodes = []
|
66
|
+
scope =
|
67
|
+
Rewriter::WithinScope.new(instance, { type: 'block' }, { including_self: false }) do
|
56
68
|
block_nodes << node
|
57
69
|
end
|
58
70
|
scope.process
|
59
71
|
expect(block_nodes.size).to eq 2
|
60
72
|
end
|
61
73
|
|
62
|
-
it 'matches only one block node if
|
74
|
+
it 'matches only one block node if recursive is false' do
|
63
75
|
block_nodes = []
|
64
76
|
scope =
|
65
|
-
Rewriter::WithinScope.new(instance, { type: 'block' }, {
|
77
|
+
Rewriter::WithinScope.new(instance, { type: 'block' }, { recursive: false }) do
|
66
78
|
block_nodes << node
|
67
79
|
end
|
68
80
|
scope.process
|
69
81
|
expect(block_nodes.size).to eq 1
|
70
82
|
end
|
71
83
|
|
72
|
-
it 'matches only one
|
84
|
+
it 'matches only one block node if stop_at_first_match is true' do
|
73
85
|
block_nodes = []
|
74
86
|
scope =
|
75
|
-
Rewriter::WithinScope.new(instance, { type: 'block' }, {
|
87
|
+
Rewriter::WithinScope.new(instance, { type: 'block' }, { stop_at_first_match: true }) do
|
76
88
|
block_nodes << node
|
77
89
|
end
|
78
90
|
scope.process
|
data/synvert-core-ruby.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_runtime_dependency "activesupport"
|
22
|
+
spec.add_runtime_dependency "activesupport", "< 7.0.0"
|
23
23
|
spec.add_runtime_dependency "erubis"
|
24
24
|
spec.add_runtime_dependency "node_query"
|
25
25
|
spec.add_runtime_dependency "node_mutation"
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: synvert-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "<"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 7.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "<"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 7.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: erubis
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- ".rspec"
|
107
107
|
- CHANGELOG.md
|
108
108
|
- Gemfile
|
109
|
+
- Gemfile.lock
|
109
110
|
- Guardfile
|
110
111
|
- LICENSE.txt
|
111
112
|
- README.md
|