synvert-core 0.63.1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.gitignore +4 -0
- data/CHANGELOG.md +9 -1
- data/Guardfile +11 -2
- data/README.md +74 -34
- data/Rakefile +15 -1
- data/lib/synvert/core/array_ext.rb +41 -0
- data/lib/synvert/core/configuration.rb +12 -0
- data/lib/synvert/core/engine/erb.rb +9 -8
- data/lib/synvert/core/exceptions.rb +0 -4
- data/lib/synvert/core/node_ext.rb +232 -128
- data/lib/synvert/core/node_query/compiler/array.rb +34 -0
- data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
- data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
- data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
- data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
- data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
- data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
- data/lib/synvert/core/node_query/compiler/float.rb +23 -0
- data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
- data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
- data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
- data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
- data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
- data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
- data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
- data/lib/synvert/core/node_query/compiler/string.rb +34 -0
- data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
- data/lib/synvert/core/node_query/compiler.rb +24 -0
- data/lib/synvert/core/node_query/lexer.rex +96 -0
- data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
- data/lib/synvert/core/node_query/parser.racc.rb +518 -0
- data/lib/synvert/core/node_query/parser.y +84 -0
- data/lib/synvert/core/node_query.rb +36 -0
- data/lib/synvert/core/rewriter/action/append_action.rb +4 -3
- data/lib/synvert/core/rewriter/action/delete_action.rb +17 -8
- data/lib/synvert/core/rewriter/action/insert_action.rb +16 -7
- data/lib/synvert/core/rewriter/action/insert_after_action.rb +3 -2
- data/lib/synvert/core/rewriter/action/prepend_action.rb +3 -2
- data/lib/synvert/core/rewriter/action/remove_action.rb +16 -10
- data/lib/synvert/core/rewriter/action/replace_action.rb +15 -5
- data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +18 -11
- data/lib/synvert/core/rewriter/action/replace_with_action.rb +6 -5
- data/lib/synvert/core/rewriter/action/wrap_action.rb +16 -7
- data/lib/synvert/core/rewriter/action.rb +22 -10
- data/lib/synvert/core/rewriter/any_value.rb +1 -0
- data/lib/synvert/core/rewriter/condition/if_exist_condition.rb +4 -0
- data/lib/synvert/core/rewriter/condition/if_only_exist_condition.rb +4 -0
- data/lib/synvert/core/rewriter/condition/unless_exist_condition.rb +4 -0
- data/lib/synvert/core/rewriter/condition.rb +11 -3
- data/lib/synvert/core/rewriter/gem_spec.rb +6 -3
- data/lib/synvert/core/rewriter/helper.rb +7 -4
- data/lib/synvert/core/rewriter/instance.rb +217 -104
- data/lib/synvert/core/rewriter/ruby_version.rb +4 -4
- data/lib/synvert/core/rewriter/scope/goto_scope.rb +5 -6
- data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
- data/lib/synvert/core/rewriter/scope/within_scope.rb +10 -5
- data/lib/synvert/core/rewriter/scope.rb +8 -0
- data/lib/synvert/core/rewriter/warning.rb +1 -1
- data/lib/synvert/core/rewriter.rb +91 -43
- data/lib/synvert/core/version.rb +1 -1
- data/lib/synvert/core.rb +22 -6
- data/spec/synvert/core/engine/erb_spec.rb +2 -2
- data/spec/synvert/core/node_ext_spec.rb +36 -12
- data/spec/synvert/core/node_query/lexer_spec.rb +512 -0
- data/spec/synvert/core/node_query/parser_spec.rb +270 -0
- data/spec/synvert/core/rewriter/action_spec.rb +0 -4
- data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
- data/spec/synvert/core/rewriter/gem_spec_spec.rb +1 -1
- data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
- data/spec/synvert/core/rewriter/instance_spec.rb +31 -20
- data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
- data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
- data/spec/synvert/core/rewriter_spec.rb +4 -2
- data/synvert-core-ruby.gemspec +7 -2
- metadata +91 -4
@@ -4,12 +4,24 @@ module Synvert::Core
|
|
4
4
|
# Instance is an execution unit, it finds specified ast nodes,
|
5
5
|
# checks if the nodes match some conditions, then add, replace or remove code.
|
6
6
|
#
|
7
|
-
# One instance can
|
7
|
+
# One instance can contain one or many {Synvert::Core::Rewriter::Scope} and {Synvert::Rewriter::Condition}.
|
8
8
|
class Rewriter::Instance
|
9
9
|
include Rewriter::Helper
|
10
|
+
# Initialize an Instance.
|
11
|
+
#
|
12
|
+
# @param rewriter [Synvert::Core::Rewriter]
|
13
|
+
# @param file_patterns [Array<String>] pattern list to find files, e.g. ['spec/**/*_spec.rb']
|
14
|
+
# @yield block code to find nodes, match conditions and rewrite code.
|
15
|
+
def initialize(rewriter, file_patterns, &block)
|
16
|
+
@rewriter = rewriter
|
17
|
+
@actions = []
|
18
|
+
@file_patterns = file_patterns
|
19
|
+
@block = block
|
20
|
+
rewriter.helpers.each { |helper| singleton_class.send(:define_method, helper[:name], &helper[:block]) }
|
21
|
+
end
|
10
22
|
|
11
23
|
class << self
|
12
|
-
#
|
24
|
+
# Get file source.
|
13
25
|
#
|
14
26
|
# @param file_path [String] file path
|
15
27
|
# @return [String] file source
|
@@ -23,7 +35,7 @@ module Synvert::Core
|
|
23
35
|
end
|
24
36
|
end
|
25
37
|
|
26
|
-
#
|
38
|
+
# Get file ast.
|
27
39
|
#
|
28
40
|
# @param file_path [String] file path
|
29
41
|
# @return [String] ast node for file
|
@@ -51,7 +63,7 @@ module Synvert::Core
|
|
51
63
|
@file_ast[file_path] = nil
|
52
64
|
end
|
53
65
|
|
54
|
-
# Reset
|
66
|
+
# Reset file source and ast.
|
55
67
|
def reset
|
56
68
|
@file_source = {}
|
57
69
|
@file_ast = {}
|
@@ -69,59 +81,15 @@ module Synvert::Core
|
|
69
81
|
self.class.file_source(current_file)
|
70
82
|
end
|
71
83
|
|
72
|
-
# Initialize an instance.
|
73
|
-
#
|
74
|
-
# @param rewriter [Synvert::Core::Rewriter]
|
75
|
-
# @param file_patterns [Array<String>] pattern list to find files, e.g. ['spec/**/*_spec.rb']
|
76
|
-
# @param block [Block] block code to find nodes, match conditions and rewrite code.
|
77
|
-
# @return [Synvert::Core::Rewriter::Instance]
|
78
|
-
def initialize(rewriter, file_patterns, &block)
|
79
|
-
@rewriter = rewriter
|
80
|
-
@actions = []
|
81
|
-
@file_patterns = file_patterns
|
82
|
-
@block = block
|
83
|
-
rewriter.helpers.each { |helper| singleton_class.send(:define_method, helper[:name], &helper[:block]) }
|
84
|
-
end
|
85
|
-
|
86
84
|
# Process the instance.
|
87
|
-
# It finds
|
88
|
-
#
|
85
|
+
# It finds specified files, for each file, it executes the block code, rewrites the original code,
|
86
|
+
# then write the code back to the original file.
|
89
87
|
def process
|
90
88
|
@file_patterns.each do |file_pattern|
|
91
89
|
Dir.glob(File.join(Configuration.path, file_pattern)).each do |file_path|
|
92
|
-
next if Configuration.skip_files.include?
|
90
|
+
next if Configuration.skip_files.include?(file_path)
|
93
91
|
|
94
|
-
|
95
|
-
puts file_path if Configuration.show_run_process
|
96
|
-
conflict_actions = []
|
97
|
-
source = +self.class.file_source(file_path)
|
98
|
-
ast = self.class.file_ast(file_path)
|
99
|
-
|
100
|
-
@current_file = file_path
|
101
|
-
|
102
|
-
process_with_node ast do
|
103
|
-
begin
|
104
|
-
instance_eval(&@block)
|
105
|
-
rescue NoMethodError
|
106
|
-
puts @current_node.debug_info
|
107
|
-
raise
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
if @actions.length > 0
|
112
|
-
@actions.sort_by! { |action| [action.begin_pos, action.end_pos] }
|
113
|
-
conflict_actions = get_conflict_actions
|
114
|
-
@actions.reverse_each do |action|
|
115
|
-
source[action.begin_pos...action.end_pos] = action.rewritten_code
|
116
|
-
end
|
117
|
-
@actions = []
|
118
|
-
|
119
|
-
update_file(file_path, source)
|
120
|
-
end
|
121
|
-
rescue Parser::SyntaxError
|
122
|
-
puts "[Warn] file #{file_path} was not parsed correctly."
|
123
|
-
# do nothing, iterate next file
|
124
|
-
end while !conflict_actions.empty?
|
92
|
+
process_file(file_path)
|
125
93
|
end
|
126
94
|
end
|
127
95
|
end
|
@@ -158,159 +126,304 @@ module Synvert::Core
|
|
158
126
|
# DSL #
|
159
127
|
#######
|
160
128
|
|
161
|
-
# Parse
|
129
|
+
# Parse +find_node+ dsl, it creates {Synvert::Core::Rewriter::QueryScope} to recursively find matching ast nodes,
|
162
130
|
# then continue operating on each matching ast node.
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
|
131
|
+
# @example
|
132
|
+
# # matches FactoryBot.create(:user)
|
133
|
+
# find_node '.send[receiver=FactoryBot][message=create][arguments.size=1]' do
|
134
|
+
# end
|
135
|
+
# @param query_string [String] query string to find matching ast nodes.
|
136
|
+
# @yield run on the matching nodes.
|
137
|
+
# @raise [Synvert::Core::NodeQuery::Compiler::ParseError] if query string is invalid.
|
138
|
+
def find_node(query_string, &block)
|
139
|
+
Rewriter::QueryScope.new(self, query_string, &block).process
|
170
140
|
end
|
171
141
|
|
172
|
-
|
173
|
-
|
174
|
-
# Parse within_direct_node dsl, it creates a [Synvert::Core::Rewriter::WithinScope] to find direct matching ast nodes,
|
142
|
+
# Parse +within_node+ dsl, it creates a {Synvert::Core::Rewriter::WithinScope} to recursively find matching ast nodes,
|
175
143
|
# then continue operating on each matching ast node.
|
176
|
-
#
|
144
|
+
# @example
|
145
|
+
# # matches User.find_by_login('test')
|
146
|
+
# with_node type: 'send', message: /^find_by_/ do
|
147
|
+
# end
|
177
148
|
# @param rules [Hash] rules to find mathing ast nodes.
|
178
|
-
# @param
|
179
|
-
|
180
|
-
|
149
|
+
# @param options [Hash] optional
|
150
|
+
# @option stop_when_match [Boolean] set if stop when match, default is false
|
151
|
+
# @option direct [Boolean] set if find direct matching ast nodes, default is false
|
152
|
+
# @yield run on the matching nodes.
|
153
|
+
def within_node(rules, options = {}, &block)
|
154
|
+
options[:stop_when_match] ||= false
|
155
|
+
options[:direct] ||= false
|
156
|
+
Rewriter::WithinScope.new(self, rules, options, &block).process
|
181
157
|
end
|
182
158
|
|
183
|
-
alias
|
159
|
+
alias with_node within_node
|
184
160
|
|
185
|
-
# Parse goto_node dsl, it creates a
|
161
|
+
# Parse +goto_node+ dsl, it creates a {Synvert::Core::Rewriter::GotoScope} to go to a child node,
|
186
162
|
# then continue operating on the child node.
|
187
|
-
#
|
163
|
+
# @example
|
164
|
+
# # head status: 406
|
165
|
+
# with_node type: 'send', receiver: nil, message: 'head', arguments: { size: 1, first: { type: 'hash' } } do
|
166
|
+
# goto_node 'arguments.first' do
|
167
|
+
# end
|
168
|
+
# end
|
188
169
|
# @param child_node_name [Symbol|String] the name of the child nodes.
|
189
170
|
# @param block [Block] block code to continue operating on the matching nodes.
|
190
171
|
def goto_node(child_node_name, &block)
|
191
172
|
Rewriter::GotoScope.new(self, child_node_name, &block).process
|
192
173
|
end
|
193
174
|
|
194
|
-
# Parse if_exist_node dsl, it creates a
|
175
|
+
# Parse +if_exist_node+ dsl, it creates a {Synvert::Core::Rewriter::IfExistCondition} to check
|
195
176
|
# if matching nodes exist in the child nodes, if so, then continue operating on each matching ast node.
|
196
|
-
#
|
177
|
+
# @example
|
178
|
+
# # Klass.any_instance.stub(:message)
|
179
|
+
# with_node type: 'send', message: 'stub', arguments: { first: { type: { not: 'hash' } } } do
|
180
|
+
# if_exist_node type: 'send', message: 'any_instance' do
|
181
|
+
# end
|
182
|
+
# end
|
197
183
|
# @param rules [Hash] rules to check mathing ast nodes.
|
198
184
|
# @param block [Block] block code to continue operating on the matching nodes.
|
199
185
|
def if_exist_node(rules, &block)
|
200
186
|
Rewriter::IfExistCondition.new(self, rules, &block).process
|
201
187
|
end
|
202
188
|
|
203
|
-
# Parse unless_exist_node dsl, it creates a
|
189
|
+
# Parse +unless_exist_node+ dsl, it creates a {Synvert::Core::Rewriter::UnlessExistCondition} to check
|
204
190
|
# if matching nodes doesn't exist in the child nodes, if so, then continue operating on each matching ast node.
|
205
|
-
#
|
191
|
+
# @example
|
192
|
+
# # obj.stub(:message)
|
193
|
+
# with_node type: 'send', message: 'stub', arguments: { first: { type: { not: 'hash' } } } do
|
194
|
+
# unless_exist_node type: 'send', message: 'any_instance' do
|
195
|
+
# end
|
196
|
+
# end
|
206
197
|
# @param rules [Hash] rules to check mathing ast nodes.
|
207
198
|
# @param block [Block] block code to continue operating on the matching nodes.
|
208
199
|
def unless_exist_node(rules, &block)
|
209
200
|
Rewriter::UnlessExistCondition.new(self, rules, &block).process
|
210
201
|
end
|
211
202
|
|
212
|
-
# Parse if_only_exist_node dsl, it creates a
|
203
|
+
# Parse +if_only_exist_node+ dsl, it creates a {Synvert::Core::Rewriter::IfOnlyExistCondition} to check
|
213
204
|
# if current node has only one child node and the child node matches rules,
|
214
205
|
# if so, then continue operating on each matching ast node.
|
215
|
-
#
|
206
|
+
# @example
|
207
|
+
# # it { should matcher }
|
208
|
+
# with_node type: 'block', caller: { message: 'it' } do
|
209
|
+
# if_only_exist_node type: 'send', receiver: nil, message: 'should' do
|
210
|
+
# end
|
211
|
+
# end
|
216
212
|
# @param rules [Hash] rules to check mathing ast nodes.
|
217
213
|
# @param block [Block] block code to continue operating on the matching nodes.
|
218
214
|
def if_only_exist_node(rules, &block)
|
219
215
|
Rewriter::IfOnlyExistCondition.new(self, rules, &block).process
|
220
216
|
end
|
221
217
|
|
222
|
-
# Parse append dsl, it creates a
|
218
|
+
# Parse +append+ dsl, it creates a {Synvert::Core::Rewriter::AppendAction} to
|
223
219
|
# append the code to the bottom of current node body.
|
224
|
-
#
|
220
|
+
# @example
|
221
|
+
# # def teardown
|
222
|
+
# # clean_something
|
223
|
+
# # end
|
224
|
+
# # =>
|
225
|
+
# # def teardown
|
226
|
+
# # clean_something
|
227
|
+
# # super
|
228
|
+
# # end
|
229
|
+
# with_node type: 'def', name: 'steardown' do
|
230
|
+
# append 'super'
|
231
|
+
# end
|
225
232
|
# @param code [String] code need to be appended.
|
226
233
|
def append(code)
|
227
234
|
@actions << Rewriter::AppendAction.new(self, code).process
|
228
235
|
end
|
229
236
|
|
230
|
-
# Parse prepend dsl, it creates a
|
237
|
+
# Parse +prepend+ dsl, it creates a {Synvert::Core::Rewriter::PrependAction} to
|
231
238
|
# prepend the code to the top of current node body.
|
232
|
-
#
|
239
|
+
# @example
|
240
|
+
# # def setup
|
241
|
+
# # do_something
|
242
|
+
# # end
|
243
|
+
# # =>
|
244
|
+
# # def setup
|
245
|
+
# # super
|
246
|
+
# # do_something
|
247
|
+
# # end
|
248
|
+
# with_node type: 'def', name: 'setup' do
|
249
|
+
# prepend 'super'
|
250
|
+
# end
|
233
251
|
# @param code [String] code need to be prepended.
|
234
252
|
def prepend(code)
|
235
253
|
@actions << Rewriter::PrependAction.new(self, code).process
|
236
254
|
end
|
237
255
|
|
238
|
-
# Parse insert dsl, it creates a
|
239
|
-
#
|
240
|
-
#
|
256
|
+
# Parse +insert+ dsl, it creates a {Synvert::Core::Rewriter::InsertAction} to insert code.
|
257
|
+
# @example
|
258
|
+
# # open('http://test.com')
|
259
|
+
# # =>
|
260
|
+
# # URI.open('http://test.com')
|
261
|
+
# with_node type: 'send', receiver: nil, message: 'open' do
|
262
|
+
# insert 'URI.', at: 'beginning'
|
263
|
+
# end
|
241
264
|
# @param code [String] code need to be inserted.
|
242
|
-
# @param at [String] insert position, beginning or end
|
265
|
+
# @param at [String] insert position, beginning or end
|
243
266
|
# @param to [String] where to insert, if it is nil, will insert to current node.
|
244
267
|
def insert(code, at: 'end', to: nil)
|
245
268
|
@actions << Rewriter::InsertAction.new(self, code, at: at, to: to).process
|
246
269
|
end
|
247
270
|
|
248
|
-
# Parse insert_after dsl, it creates a
|
271
|
+
# Parse +insert_after+ dsl, it creates a {Synvert::Core::Rewriter::InsertAfterAction} to
|
249
272
|
# insert the code next to the current node.
|
250
|
-
#
|
273
|
+
# @example
|
274
|
+
# # Synvert::Application.config.secret_token = "0447aa931d42918bfb934750bb78257088fb671186b5d1b6f9fddf126fc8a14d34f1d045cefab3900751c3da121a8dd929aec9bafe975f1cabb48232b4002e4e"
|
275
|
+
# # =>
|
276
|
+
# # Synvert::Application.config.secret_token = "0447aa931d42918bfb934750bb78257088fb671186b5d1b6f9fddf126fc8a14d34f1d045cefab3900751c3da121a8dd929aec9bafe975f1cabb48232b4002e4e"
|
277
|
+
# # Synvert::Application.config.secret_key_base = "bf4f3f46924ecd9adcb6515681c78144545bba454420973a274d7021ff946b8ef043a95ca1a15a9d1b75f9fbdf85d1a3afaf22f4e3c2f3f78e24a0a188b581df"
|
278
|
+
# with_node type: 'send', message: 'secret_token=' do
|
279
|
+
# insert_after "{{receiver}}.secret_key_base = \"#{SecureRandom.hex(64)}\""
|
280
|
+
# end
|
251
281
|
# @param code [String] code need to be inserted.
|
252
|
-
def insert_after(
|
253
|
-
@actions << Rewriter::InsertAfterAction.new(self,
|
282
|
+
def insert_after(code)
|
283
|
+
@actions << Rewriter::InsertAfterAction.new(self, code).process
|
254
284
|
end
|
255
285
|
|
256
|
-
# Parse replace_with dsl, it creates a
|
257
|
-
# replace current node
|
258
|
-
#
|
286
|
+
# Parse +replace_with+ dsl, it creates a {Synvert::Core::Rewriter::ReplaceWithAction} to
|
287
|
+
# replace the whole code of current node.
|
288
|
+
# @example
|
289
|
+
# # obj.stub(:foo => 1, :bar => 2)
|
290
|
+
# # =>
|
291
|
+
# # allow(obj).to receive_messages(:foo => 1, :bar => 2)
|
292
|
+
# with_node type: 'send', message: 'stub', arguments: { first: { type: 'hash' } } do
|
293
|
+
# replace_with 'allow({{receiver}}).to receive_messages({{arguments}})'
|
294
|
+
# end
|
259
295
|
# @param code [String] code need to be replaced with.
|
260
296
|
def replace_with(code)
|
261
297
|
@actions << Rewriter::ReplaceWithAction.new(self, code).process
|
262
298
|
end
|
263
299
|
|
264
|
-
# Parse replace
|
265
|
-
# replace child nodes
|
266
|
-
#
|
300
|
+
# Parse +replace+ dsl, it creates a {Synvert::Core::Rewriter::ReplaceAction} to
|
301
|
+
# replace the code of specified child nodes.
|
302
|
+
# @example
|
303
|
+
# # assert(object.empty?)
|
304
|
+
# # =>
|
305
|
+
# # assert_empty(object)
|
306
|
+
# with_node type: 'send', receiver: nil, message: 'assert', arguments: { size: 1, first: { type: 'send', message: 'empty?', arguments: { size: 0 } } } do
|
307
|
+
# replace :message, with: 'assert_empty'
|
308
|
+
# replace :arguments, with: '{{arguments.first.receiver}}'
|
309
|
+
# end
|
267
310
|
# @param selectors [Array<Symbol>] selector names of child node.
|
268
311
|
# @param with [String] code need to be replaced with.
|
269
312
|
def replace(*selectors, with:)
|
270
313
|
@actions << Rewriter::ReplaceAction.new(self, *selectors, with: with).process
|
271
314
|
end
|
272
315
|
|
273
|
-
# Parse replace_erb_stmt_with_expr dsl, it creates a
|
316
|
+
# Parse +replace_erb_stmt_with_expr+ dsl, it creates a {Synvert::Core::Rewriter::ReplaceErbStmtWithExprAction} to
|
274
317
|
# replace erb stmt code to expr code.
|
318
|
+
# @example
|
319
|
+
# # <% form_for post do |f| %>
|
320
|
+
# # <% end %>
|
321
|
+
# # =>
|
322
|
+
# # <%= form_for post do |f| %>
|
323
|
+
# # <% end %>
|
324
|
+
# with_node type: 'block', caller: { type: 'send', receiver: nil, message: 'form_for' } do
|
325
|
+
# replace_erb_stmt_with_expr
|
326
|
+
# end
|
275
327
|
def replace_erb_stmt_with_expr
|
276
328
|
@actions << Rewriter::ReplaceErbStmtWithExprAction.new(self).process
|
277
329
|
end
|
278
330
|
|
279
|
-
# Parse remove dsl, it creates a
|
331
|
+
# Parse +remove+ dsl, it creates a {Synvert::Core::Rewriter::RemoveAction} to remove current node.
|
332
|
+
# @example
|
333
|
+
# with_node type: 'send', message: { in: %w[puts p] } do
|
334
|
+
# remove
|
335
|
+
# end
|
280
336
|
def remove
|
281
337
|
@actions << Rewriter::RemoveAction.new(self).process
|
282
338
|
end
|
283
339
|
|
284
|
-
# Parse delete dsl, it creates a
|
285
|
-
#
|
340
|
+
# Parse +delete+ dsl, it creates a {Synvert::Core::Rewriter::DeleteAction} to delete child nodes.
|
341
|
+
# @example
|
342
|
+
# # FactoryBot.create(...)
|
343
|
+
# # =>
|
344
|
+
# # create(...)
|
345
|
+
# with_node type: 'send', receiver: 'FactoryBot', message: 'create' do
|
346
|
+
# delete :receiver, :dot
|
347
|
+
# end
|
286
348
|
# @param selectors [Array<Symbol>] selector names of child node.
|
287
349
|
def delete(*selectors)
|
288
350
|
@actions << Rewriter::DeleteAction.new(self, *selectors).process
|
289
351
|
end
|
290
352
|
|
291
|
-
# Parse wrap
|
353
|
+
# Parse +wrap+ dsl, it creates a {Synvert::Core::Rewriter::WrapAction} to
|
292
354
|
# wrap current node with code.
|
293
|
-
#
|
355
|
+
# @example
|
356
|
+
# # class Foobar
|
357
|
+
# # end
|
358
|
+
# # =>
|
359
|
+
# # module Synvert
|
360
|
+
# # class Foobar
|
361
|
+
# # end
|
362
|
+
# # end
|
363
|
+
# within_node type: 'class' do
|
364
|
+
# wrap with: 'module Synvert'
|
365
|
+
# end
|
294
366
|
# @param with [String] code need to be wrapped with.
|
295
|
-
# @param indent [Integer] number of whitespaces.
|
367
|
+
# @param indent [Integer, nil] number of whitespaces.
|
296
368
|
def wrap(with:, indent: nil)
|
297
369
|
@actions << Rewriter::WrapAction.new(self, with: with, indent: indent).process
|
298
370
|
end
|
299
371
|
|
300
|
-
# Parse warn dsl, it creates a
|
301
|
-
#
|
372
|
+
# Parse +warn+ dsl, it creates a {Synvert::Core::Rewriter::Warning} to save warning message.
|
373
|
+
# @example
|
374
|
+
# within_files 'vendor/plugins' do
|
375
|
+
# warn 'Rails::Plugin is deprecated and will be removed in Rails 4.0. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies.'
|
376
|
+
# end
|
302
377
|
# @param message [String] warning message.
|
303
378
|
def warn(message)
|
304
379
|
@rewriter.add_warning Rewriter::Warning.new(self, message)
|
305
380
|
end
|
306
381
|
|
307
|
-
#
|
382
|
+
# Match any value but nil.
|
383
|
+
# @example
|
384
|
+
# type: 'hash', nothing_value: 'true', status_value: any_value
|
385
|
+
# @return [Synvert::Core::Rewriter::AnyValue]
|
308
386
|
def any_value
|
309
387
|
Rewriter::AnyValue.new
|
310
388
|
end
|
311
389
|
|
312
390
|
private
|
313
391
|
|
392
|
+
# Process one file.
|
393
|
+
#
|
394
|
+
# @param file_path [String]
|
395
|
+
def process_file(file_path)
|
396
|
+
begin
|
397
|
+
puts file_path if Configuration.show_run_process
|
398
|
+
conflict_actions = []
|
399
|
+
source = +self.class.file_source(file_path)
|
400
|
+
ast = self.class.file_ast(file_path)
|
401
|
+
|
402
|
+
@current_file = file_path
|
403
|
+
|
404
|
+
process_with_node(ast) do
|
405
|
+
instance_eval(&@block)
|
406
|
+
rescue NoMethodError
|
407
|
+
puts @current_node.debug_info
|
408
|
+
raise
|
409
|
+
end
|
410
|
+
|
411
|
+
if @actions.length > 0
|
412
|
+
@actions.sort_by! { |action| [action.begin_pos, action.end_pos] }
|
413
|
+
conflict_actions = get_conflict_actions
|
414
|
+
@actions.reverse_each do |action|
|
415
|
+
source[action.begin_pos...action.end_pos] = action.rewritten_code
|
416
|
+
end
|
417
|
+
@actions = []
|
418
|
+
|
419
|
+
update_file(file_path, source)
|
420
|
+
end
|
421
|
+
rescue Parser::SyntaxError
|
422
|
+
puts "[Warn] file #{file_path} was not parsed correctly."
|
423
|
+
# do nothing, iterate next file
|
424
|
+
end while !conflict_actions.empty?
|
425
|
+
end
|
426
|
+
|
314
427
|
# It changes source code from bottom to top, and it can change source code twice at the same time,
|
315
428
|
# So if there is an overlap between two actions, it removes the conflict actions and operate them in the next loop.
|
316
429
|
def get_conflict_actions
|
@@ -17,13 +17,13 @@ module Synvert::Core
|
|
17
17
|
# @return [Boolean] true if matches, otherwise false.
|
18
18
|
def match?
|
19
19
|
if File.exist?(File.join(Configuration.path, '.ruby-version'))
|
20
|
-
|
20
|
+
version_file = '.ruby-version'
|
21
21
|
elsif File.exist?(File.join(Configuration.path, '.rvmrc'))
|
22
|
-
|
22
|
+
version_file = '.rvmrc'
|
23
23
|
end
|
24
|
-
return true unless
|
24
|
+
return true unless version_file
|
25
25
|
|
26
|
-
version = File.read(File.join(Configuration.path,
|
26
|
+
version = File.read(File.join(Configuration.path, version_file))
|
27
27
|
Gem::Version.new(version) >= Gem::Version.new(@version)
|
28
28
|
end
|
29
29
|
end
|
@@ -3,18 +3,17 @@
|
|
3
3
|
module Synvert::Core
|
4
4
|
# Go to and change its scope to a child node.
|
5
5
|
class Rewriter::GotoScope < Rewriter::Scope
|
6
|
-
# Initialize a
|
6
|
+
# Initialize a GotoScope.
|
7
7
|
#
|
8
8
|
# @param instance [Synvert::Core::Rewriter::Instance]
|
9
|
-
# @param child_node_name [Symbol|
|
10
|
-
# @
|
9
|
+
# @param child_node_name [Symbol|String] name of child node
|
10
|
+
# @yield run on the child node
|
11
11
|
def initialize(instance, child_node_name, &block)
|
12
|
-
|
12
|
+
super(instance, &block)
|
13
13
|
@child_node_name = child_node_name
|
14
|
-
@block = block
|
15
14
|
end
|
16
15
|
|
17
|
-
# Go to a child now, then run the block code
|
16
|
+
# Go to a child now, then run the block code on the the child node.
|
18
17
|
def process
|
19
18
|
current_node = @instance.current_node
|
20
19
|
return unless current_node
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Synvert::Core
|
4
|
+
# QueryScope finds out nodes by using node query language, then changes its scope to matching node.
|
5
|
+
class Rewriter::QueryScope < Rewriter::Scope
|
6
|
+
# Initialize a QueryScope.
|
7
|
+
#
|
8
|
+
# @param instance [Synvert::Core::Rewriter::Instance]
|
9
|
+
# @param query_string [String]
|
10
|
+
# @yield run on all matching nodes
|
11
|
+
def initialize(instance, query_string, &block)
|
12
|
+
super(instance, &block)
|
13
|
+
@query_string = query_string
|
14
|
+
end
|
15
|
+
|
16
|
+
# Find out the matching nodes.
|
17
|
+
#
|
18
|
+
# It checks the current node and iterates all child nodes,
|
19
|
+
# then run the block code on each matching node.
|
20
|
+
# @raise [Synvert::Core::NodeQuery::Compiler::ParseError] if the query string is invalid.
|
21
|
+
def process
|
22
|
+
current_node = @instance.current_node
|
23
|
+
return unless current_node
|
24
|
+
|
25
|
+
@instance.process_with_node(current_node) do
|
26
|
+
NodeQuery::Parser.new.parse(@query_string).query_nodes(current_node).each do |node|
|
27
|
+
@instance.process_with_node(node) do
|
28
|
+
@instance.instance_eval(&@block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
rescue NodeQuery::Lexer::ScanError, Racc::ParseError => e
|
33
|
+
raise NodeQuery::Compiler::ParseError, "Invalid query string: #{@query_string}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -3,17 +3,16 @@
|
|
3
3
|
module Synvert::Core
|
4
4
|
# WithinScope finds out nodes which match rules, then changes its scope to matching node.
|
5
5
|
class Rewriter::WithinScope < Rewriter::Scope
|
6
|
-
# Initialize a
|
6
|
+
# Initialize a WithinScope.
|
7
7
|
#
|
8
8
|
# @param instance [Synvert::Core::Rewriter::Instance]
|
9
9
|
# @param rules [Hash]
|
10
10
|
# @param options [Hash]
|
11
|
-
# @
|
11
|
+
# @yield run on all matching nodes
|
12
12
|
def initialize(instance, rules, options = {}, &block)
|
13
|
-
|
13
|
+
super(instance, &block)
|
14
14
|
@rules = rules
|
15
15
|
@options = options
|
16
|
-
@block = block
|
17
16
|
end
|
18
17
|
|
19
18
|
# Find out the matching nodes.
|
@@ -43,6 +42,8 @@ module Synvert::Core
|
|
43
42
|
private
|
44
43
|
|
45
44
|
# Find the matching nodes only in current or direct children.
|
45
|
+
#
|
46
|
+
# @param current_node [Parser::AST::Node]
|
46
47
|
def find_direct_matching_nodes(current_node)
|
47
48
|
matching_nodes = []
|
48
49
|
if current_node.is_a?(Parser::AST::Node)
|
@@ -62,6 +63,8 @@ module Synvert::Core
|
|
62
63
|
end
|
63
64
|
|
64
65
|
# Find matching nodes in all recursive children.
|
66
|
+
#
|
67
|
+
# @param current_node [Parser::AST::Node]
|
65
68
|
def find_recursive_matching_nodes(current_node)
|
66
69
|
matching_nodes = []
|
67
70
|
if current_node.is_a?(Parser::AST::Node)
|
@@ -81,6 +84,8 @@ module Synvert::Core
|
|
81
84
|
end
|
82
85
|
|
83
86
|
# Find matching nodes in recursive children but do not continue on matching nodes.
|
87
|
+
#
|
88
|
+
# @param current_node [Parser::AST::Node]
|
84
89
|
def find_matching_nodes(current_node)
|
85
90
|
matching_nodes = []
|
86
91
|
if current_node.is_a?(Parser::AST::Node)
|
@@ -111,4 +116,4 @@ module Synvert::Core
|
|
111
116
|
matching_nodes
|
112
117
|
end
|
113
118
|
end
|
114
|
-
end
|
119
|
+
end
|
@@ -3,5 +3,13 @@
|
|
3
3
|
module Synvert::Core
|
4
4
|
# Scope finds out nodes which match rules.
|
5
5
|
class Rewriter::Scope
|
6
|
+
# Initialize a Scope
|
7
|
+
#
|
8
|
+
# @param instance [Synvert::Core::Rewriter::Instance]
|
9
|
+
# @yield run on a scope
|
10
|
+
def initialize(instance, &block)
|
11
|
+
@instance = instance
|
12
|
+
@block = block
|
13
|
+
end
|
6
14
|
end
|
7
15
|
end
|