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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/.gitignore +4 -0
  4. data/CHANGELOG.md +9 -1
  5. data/Guardfile +11 -2
  6. data/README.md +74 -34
  7. data/Rakefile +15 -1
  8. data/lib/synvert/core/array_ext.rb +41 -0
  9. data/lib/synvert/core/configuration.rb +12 -0
  10. data/lib/synvert/core/engine/erb.rb +9 -8
  11. data/lib/synvert/core/exceptions.rb +0 -4
  12. data/lib/synvert/core/node_ext.rb +232 -128
  13. data/lib/synvert/core/node_query/compiler/array.rb +34 -0
  14. data/lib/synvert/core/node_query/compiler/attribute.rb +51 -0
  15. data/lib/synvert/core/node_query/compiler/attribute_list.rb +24 -0
  16. data/lib/synvert/core/node_query/compiler/boolean.rb +23 -0
  17. data/lib/synvert/core/node_query/compiler/comparable.rb +79 -0
  18. data/lib/synvert/core/node_query/compiler/dynamic_attribute.rb +51 -0
  19. data/lib/synvert/core/node_query/compiler/expression.rb +88 -0
  20. data/lib/synvert/core/node_query/compiler/float.rb +23 -0
  21. data/lib/synvert/core/node_query/compiler/identifier.rb +41 -0
  22. data/lib/synvert/core/node_query/compiler/integer.rb +23 -0
  23. data/lib/synvert/core/node_query/compiler/invalid_operator_error.rb +7 -0
  24. data/lib/synvert/core/node_query/compiler/nil.rb +23 -0
  25. data/lib/synvert/core/node_query/compiler/parse_error.rb +7 -0
  26. data/lib/synvert/core/node_query/compiler/regexp.rb +37 -0
  27. data/lib/synvert/core/node_query/compiler/selector.rb +51 -0
  28. data/lib/synvert/core/node_query/compiler/string.rb +34 -0
  29. data/lib/synvert/core/node_query/compiler/symbol.rb +23 -0
  30. data/lib/synvert/core/node_query/compiler.rb +24 -0
  31. data/lib/synvert/core/node_query/lexer.rex +96 -0
  32. data/lib/synvert/core/node_query/lexer.rex.rb +293 -0
  33. data/lib/synvert/core/node_query/parser.racc.rb +518 -0
  34. data/lib/synvert/core/node_query/parser.y +84 -0
  35. data/lib/synvert/core/node_query.rb +36 -0
  36. data/lib/synvert/core/rewriter/action/append_action.rb +4 -3
  37. data/lib/synvert/core/rewriter/action/delete_action.rb +17 -8
  38. data/lib/synvert/core/rewriter/action/insert_action.rb +16 -7
  39. data/lib/synvert/core/rewriter/action/insert_after_action.rb +3 -2
  40. data/lib/synvert/core/rewriter/action/prepend_action.rb +3 -2
  41. data/lib/synvert/core/rewriter/action/remove_action.rb +16 -10
  42. data/lib/synvert/core/rewriter/action/replace_action.rb +15 -5
  43. data/lib/synvert/core/rewriter/action/replace_erb_stmt_with_expr_action.rb +18 -11
  44. data/lib/synvert/core/rewriter/action/replace_with_action.rb +6 -5
  45. data/lib/synvert/core/rewriter/action/wrap_action.rb +16 -7
  46. data/lib/synvert/core/rewriter/action.rb +22 -10
  47. data/lib/synvert/core/rewriter/any_value.rb +1 -0
  48. data/lib/synvert/core/rewriter/condition/if_exist_condition.rb +4 -0
  49. data/lib/synvert/core/rewriter/condition/if_only_exist_condition.rb +4 -0
  50. data/lib/synvert/core/rewriter/condition/unless_exist_condition.rb +4 -0
  51. data/lib/synvert/core/rewriter/condition.rb +11 -3
  52. data/lib/synvert/core/rewriter/gem_spec.rb +6 -3
  53. data/lib/synvert/core/rewriter/helper.rb +7 -4
  54. data/lib/synvert/core/rewriter/instance.rb +217 -104
  55. data/lib/synvert/core/rewriter/ruby_version.rb +4 -4
  56. data/lib/synvert/core/rewriter/scope/goto_scope.rb +5 -6
  57. data/lib/synvert/core/rewriter/scope/query_scope.rb +36 -0
  58. data/lib/synvert/core/rewriter/scope/within_scope.rb +10 -5
  59. data/lib/synvert/core/rewriter/scope.rb +8 -0
  60. data/lib/synvert/core/rewriter/warning.rb +1 -1
  61. data/lib/synvert/core/rewriter.rb +91 -43
  62. data/lib/synvert/core/version.rb +1 -1
  63. data/lib/synvert/core.rb +22 -6
  64. data/spec/synvert/core/engine/erb_spec.rb +2 -2
  65. data/spec/synvert/core/node_ext_spec.rb +36 -12
  66. data/spec/synvert/core/node_query/lexer_spec.rb +512 -0
  67. data/spec/synvert/core/node_query/parser_spec.rb +270 -0
  68. data/spec/synvert/core/rewriter/action_spec.rb +0 -4
  69. data/spec/synvert/core/rewriter/condition/if_only_exist_condition_spec.rb +1 -6
  70. data/spec/synvert/core/rewriter/gem_spec_spec.rb +1 -1
  71. data/spec/synvert/core/rewriter/helper_spec.rb +4 -1
  72. data/spec/synvert/core/rewriter/instance_spec.rb +31 -20
  73. data/spec/synvert/core/rewriter/scope/query_scope_spec.rb +74 -0
  74. data/spec/synvert/core/rewriter/scope/within_scope_spec.rb +12 -9
  75. data/spec/synvert/core/rewriter_spec.rb +4 -2
  76. data/synvert-core-ruby.gemspec +7 -2
  77. 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 contains one or many [Synvert::Core::Rewriter::Scope] and [Synvert::Rewriter::Condition].
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
- # Cached file source.
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
- # Cached file ast.
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 cached file source and ast.
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 all files, for each file, it executes the block code, gets all rewrite actions,
88
- # and rewrite source code back to original file.
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? file_path
90
+ next if Configuration.skip_files.include?(file_path)
93
91
 
94
- begin
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 within_node dsl, it creates a [Synvert::Core::Rewriter::WithinScope] to find recursive matching ast nodes,
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
- # @param rules [Hash] rules to find mathing ast nodes.
165
- # @param options [Hash] optional, set if stop_when_match or not.
166
- # @param block [Block] block code to continue operating on the matching nodes.
167
- def within_node(rules, options = nil, &block)
168
- options ||= { stop_when_match: false }
169
- Rewriter::WithinScope.new(self, rules, options, &block).process
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
- alias with_node within_node
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 block [Block] block code to continue operating on the matching nodes.
179
- def within_direct_node(rules, &block)
180
- Rewriter::WithinScope.new(self, rules, { direct: true }, &block).process
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 with_direct_node within_direct_node
159
+ alias with_node within_node
184
160
 
185
- # Parse goto_node dsl, it creates a [Synvert::Core::Rewriter::GotoScope] to go to a child node,
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 [Synvert::Core::Rewriter::IfExistCondition] to check
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 [Synvert::Core::Rewriter::UnlessExistCondition] to check
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 [Synvert::Core::Rewriter::IfOnlyExistCondition] to check
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 [Synvert::Core::Rewriter::AppendAction] to
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 [Synvert::Core::Rewriter::PrependAction] to
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 [Synvert::Core::Rewriter::InsertAction] to
239
- # insert the code to the top of current node body.
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, end is the default.
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 [Synvert::Core::Rewriter::InsertAfterAction] to
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(node)
253
- @actions << Rewriter::InsertAfterAction.new(self, node).process
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 [Synvert::Core::Rewriter::ReplaceWithAction] to
257
- # replace current node with code.
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 with dsl, it creates a [Synvert::Core::Rewriter::ReplaceAction] to
265
- # replace child nodes with code.
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 [Synvert::Core::Rewriter::ReplaceErbStmtWithExprAction] to
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 [Synvert::Core::Rewriter::RemoveAction] to remove current node.
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 [Synvert::Core::Rewriter::DeleteAction] to delete child nodes.
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 with dsl, it creates a [Synvert::Core::Rewriter::WrapAction] to
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 [Synvert::Core::Rewriter::Warning] to save warning message.
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
- # Any value but nil.
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
- versionFile = '.ruby-version'
20
+ version_file = '.ruby-version'
21
21
  elsif File.exist?(File.join(Configuration.path, '.rvmrc'))
22
- versionFile = '.rvmrc'
22
+ version_file = '.rvmrc'
23
23
  end
24
- return true unless versionFile
24
+ return true unless version_file
25
25
 
26
- version = File.read(File.join(Configuration.path, versionFile))
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 scope
6
+ # Initialize a GotoScope.
7
7
  #
8
8
  # @param instance [Synvert::Core::Rewriter::Instance]
9
- # @param child_node_name [Symbol|string]
10
- # @param block [Block]
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
- @instance = instance
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 with the the child node.
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 scope
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
- # @param block [Block]
11
+ # @yield run on all matching nodes
12
12
  def initialize(instance, rules, options = {}, &block)
13
- @instance = instance
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
@@ -3,7 +3,7 @@
3
3
  module Synvert::Core
4
4
  # Warning is used to save warning message.
5
5
  class Rewriter::Warning
6
- # Initialize a warning.
6
+ # Initialize a Warning.
7
7
  #
8
8
  # @param instance [Synvert::Core::Rewriter::Instance]
9
9
  # @param message [String] warning message.