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.
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.