synvert-core 0.63.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|