synvert 0.0.13 → 0.0.14

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +4 -2
  5. data/lib/synvert/cli.rb +87 -18
  6. data/lib/synvert/configuration.rb +9 -0
  7. data/lib/synvert/exceptions.rb +13 -0
  8. data/lib/synvert/node_ext.rb +106 -13
  9. data/lib/synvert/rewriter/action.rb +76 -0
  10. data/lib/synvert/rewriter/condition.rb +14 -0
  11. data/lib/synvert/rewriter/gem_spec.rb +12 -1
  12. data/lib/synvert/rewriter/instance.rb +69 -0
  13. data/lib/synvert/rewriter/scope.rb +10 -0
  14. data/lib/synvert/rewriter.rb +120 -11
  15. data/lib/synvert/snippets/factory_girl/syntax_methods.rb +51 -1
  16. data/lib/synvert/snippets/rails/convert_dynamic_finders.rb +12 -1
  17. data/lib/synvert/snippets/rails/strong_parameters.rb +22 -1
  18. data/lib/synvert/snippets/rails/upgrade_3_0_to_3_1.rb +48 -7
  19. data/lib/synvert/snippets/rails/upgrade_3_1_to_3_2.rb +14 -1
  20. data/lib/synvert/snippets/rails/upgrade_3_2_to_4_0.rb +90 -19
  21. data/lib/synvert/snippets/rspec/be_close_to_be_within.rb +7 -1
  22. data/lib/synvert/snippets/rspec/block_to_expect.rb +9 -1
  23. data/lib/synvert/snippets/rspec/boolean_matcher.rb +8 -1
  24. data/lib/synvert/snippets/rspec/collection_matcher.rb +12 -1
  25. data/lib/synvert/snippets/rspec/its_to_it.rb +34 -1
  26. data/lib/synvert/snippets/rspec/message_expectation.rb +14 -1
  27. data/lib/synvert/snippets/rspec/method_stub.rb +22 -1
  28. data/lib/synvert/snippets/rspec/negative_error_expectation.rb +8 -1
  29. data/lib/synvert/snippets/rspec/new_syntax.rb +5 -1
  30. data/lib/synvert/snippets/rspec/one_liner_expectation.rb +20 -1
  31. data/lib/synvert/snippets/rspec/should_to_expect.rb +15 -1
  32. data/lib/synvert/snippets/rspec/stub_and_mock_to_double.rb +8 -1
  33. data/lib/synvert/snippets/ruby/new_hash_syntax.rb +7 -1
  34. data/lib/synvert/snippets/ruby/new_lambda_syntax.rb +7 -1
  35. data/lib/synvert/version.rb +1 -1
  36. data/lib/synvert.rb +2 -1
  37. data/spec/synvert/rewriter/gem_spec_spec.rb +2 -2
  38. data/spec/synvert/rewriter_spec.rb +71 -34
  39. data/spec/synvert/snippets/rails/upgrade_3_0_to_3_1_spec.rb +2 -2
  40. metadata +4 -4
  41. data/lib/synvert/rewriter_not_found.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c9b185412549d5579efb12b82033786c586cc6f7
4
- data.tar.gz: cce0f663f05dc9a481edb2f46c41d4e3e0ee2ed2
3
+ metadata.gz: 263f00fa376f7527f7915d2239c759f134ef5749
4
+ data.tar.gz: f7f14d873285dfc5092e48a959e2e199186b504a
5
5
  SHA512:
6
- metadata.gz: 796bf4d58f0a30083a1522b70ff8d6cc746d5c982e0081df157a073278dc0e99c45e977390211adaea88979e12019b5da69972eac7fe667ff7df076ab4c2b5fe
7
- data.tar.gz: 05d8b0d8c84a2141fa93c245f4d5a7be3095b86c1782bee74fa2656db3386f4e20e33c9d5e34d9d14e3853fd440869f8619fbbf5cce9c396a3edac4fe98705dd
6
+ metadata.gz: 9d294b839f70a0f8a8243dbba1e6fa8735322016d9292db89992933129065dcffcee790becee77218eaaa47bfc85b14191f8df1b04616403f7649f47c38de54d
7
+ data.tar.gz: 1a3654f5b903b1398bfedf7078381505b69c83cbac8600bd0f3c57251240c343a2ea061b7b71339af39dd1c44826327d0a3b879075d958de5a72f40149eeb0da
data/.travis.yml CHANGED
@@ -3,3 +3,4 @@ rvm:
3
3
  - 1.9.3
4
4
  - 2.0.0
5
5
  - 2.1.0
6
+ - 2.1.1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.0.14
4
+
5
+ * Complement code comments.
6
+ * Add MethodNotSupported and RewriterNotFound exceptions.
7
+ * Add description dsl to define multi lines description.
8
+ * Improve cli, query snippets and show snippet.
9
+ * Process rewriter in sandbox mode.
10
+
3
11
  ## 0.0.13
4
12
 
5
13
  * Add keys and values rules for hash node
data/README.md CHANGED
@@ -26,17 +26,19 @@ $ synvert -h
26
26
  Usage: synvert [project_path]
27
27
  -d, --load SNIPPET_PATHS load additional snippets, snippet paths can be local file path or remote http url
28
28
  -l, --list list all available snippets
29
+ -q, --query QUERY query specified snippets
30
+ -s, --show SNIPPET_NAME show specified snippet description
29
31
  -r, --run SNIPPET_NAMES run specified snippets
30
32
  ```
31
33
 
32
34
  e.g.
33
35
 
34
36
  ```
35
- $ synvert --list-snippets
37
+ $ synvert -l
36
38
  ```
37
39
 
38
40
  ```
39
- $ synvert --run-snippets factory_girl_short_syntax,upgrade_rails_3_2_to_4_0 ~/Sites/railsbp/rails-bestpractices.com
41
+ $ synvert -r factory_girl_short_syntax,upgrade_rails_3_2_to_4_0 ~/Sites/railsbp/rails-bestpractices.com
40
42
  ```
41
43
 
42
44
  ## Snippets
data/lib/synvert/cli.rb CHANGED
@@ -1,36 +1,83 @@
1
1
  # coding: utf-8
2
2
  require 'optparse'
3
- require 'find'
4
3
  require 'open-uri'
5
4
 
6
5
  module Synvert
6
+ # Synvert command line interface.
7
7
  class CLI
8
+ # Initialize the cli and run.
9
+ #
10
+ # @param args [Array] arguments, default is ARGV.
11
+ # @return [Boolean] true if command runs successfully.
8
12
  def self.run(args = ARGV)
9
13
  new.run(args)
10
14
  end
11
15
 
16
+ # Initialize a CLI.
17
+ def initialize
18
+ @options = {command: 'run', snippet_paths: [], snippet_names: []}
19
+ end
20
+
21
+ # Run the CLI.
22
+ # @param args [Array] arguments.
23
+ # @return [Boolean] true if command runs successfully.
12
24
  def run(args)
13
- Configuration.instance.set 'snippet_paths', []
14
- Configuration.instance.set 'snippet_names', []
25
+ run_option_parser(args)
26
+ load_rewriters
15
27
 
16
- command = :run
28
+ case @options[:command]
29
+ when 'list' then list_available_rewriters
30
+ when 'query' then query_available_rewriters
31
+ when 'show' then show_rewriter
32
+ else
33
+ @options[:snippet_names].each do |snippet_name|
34
+ puts "===== #{snippet_name} started ====="
35
+ rewriter = Rewriter.call snippet_name
36
+ puts rewriter.todo if rewriter.todo
37
+ puts "===== #{snippet_name} done ====="
38
+ end
39
+ end
40
+ true
41
+ rescue SystemExit
42
+ true
43
+ rescue Exception => e
44
+ puts "Error: " + e.message
45
+ false
46
+ end
47
+
48
+ private
49
+
50
+ # Run OptionParser to parse arguments.
51
+ def run_option_parser(args)
17
52
  optparse = OptionParser.new do |opts|
18
53
  opts.banner = "Usage: synvert [project_path]"
19
54
  opts.on '-d', '--load SNIPPET_PATHS', 'load additional snippets, snippet paths can be local file path or remote http url' do |snippet_paths|
20
- Configuration.instance.set 'snippet_paths', snippet_paths.split(',')
55
+ @options[:snippet_paths] = snippet_paths.split(',').map(&:strip)
21
56
  end
22
57
  opts.on '-l', '--list', 'list all available snippets' do
23
- command = :list
58
+ @options[:command] = 'list'
59
+ end
60
+ opts.on '-q', '--query QUERY', 'query specified snippets' do |query|
61
+ @options[:command] = 'query'
62
+ @options[:query] = query
63
+ end
64
+ opts.on '-s', '--show SNIPPET_NAME', 'show specified snippet description' do |snippet_name|
65
+ @options[:command] = 'show'
66
+ @options[:snippet_name] = snippet_name
24
67
  end
25
68
  opts.on '-r', '--run SNIPPET_NAMES', 'run specified snippets' do |snippet_names|
26
- Configuration.instance.set 'snippet_names', snippet_names.split(',')
69
+ @options[:snippet_names] = snippet_names.split(',').map(&:strip)
27
70
  end
28
71
  end
29
72
  paths = optparse.parse(args)
30
73
  Configuration.instance.set :path, paths.first || Dir.pwd
74
+ end
31
75
 
76
+ # Load all rewriters.
77
+ def load_rewriters
32
78
  Dir.glob(File.join(File.dirname(__FILE__), 'snippets/**/*.rb')).each { |file| eval(File.read(file)) }
33
- Configuration.instance.get('snippet_paths').each do |snippet_path|
79
+
80
+ @options[:snippet_paths].each do |snippet_path|
34
81
  if snippet_path =~ /^http/
35
82
  uri = URI.parse snippet_path
36
83
  eval(uri.read)
@@ -38,19 +85,41 @@ module Synvert
38
85
  eval(File.read(snippet_path))
39
86
  end
40
87
  end
41
- Configuration.instance.get('snippet_names').each do |snippet_name|
42
- puts "===== #{snippet_name} started ====="
43
- rewriter = Rewriter.call snippet_name
44
- puts rewriter.todo_list if rewriter.todo_list
45
- puts "===== #{snippet_name} done ====="
88
+ end
89
+
90
+ # List and print all available rewriters.
91
+ def list_available_rewriters
92
+ Rewriter.availables.each do |rewriter|
93
+ print rewriter.name + " "
46
94
  end
95
+ puts
96
+ end
97
+
98
+ # Query and print available rewriters.
99
+ def query_available_rewriters
100
+ Rewriter.availables.each do |rewriter|
101
+ if rewriter.name.include? @options[:query]
102
+ print rewriter.name + " "
103
+ end
104
+ end
105
+ puts
106
+ end
47
107
 
48
- if :list == command
49
- puts "%-40s %s" % ['name', 'description']
50
- puts "%-40s %s" % ['----', '-----------']
51
- Rewriter.availables.each do |rewriter|
52
- puts "%-40s %s" % [rewriter.name, rewriter.description]
108
+ # Show and print one rewriter.
109
+ def show_rewriter
110
+ rewriter = Rewriter.fetch(@options[:snippet_name])
111
+ if rewriter
112
+ rewriter.process_with_sandbox
113
+ puts rewriter.description
114
+ rewriter.sub_snippets.each do |sub_rewriter|
115
+ puts
116
+ puts "=" * 80
117
+ puts "snippet: #{sub_rewriter.name}"
118
+ puts "=" * 80
119
+ puts sub_rewriter.description
53
120
  end
121
+ else
122
+ puts "snippet #{@options[:snippet_name]} not found"
54
123
  end
55
124
  end
56
125
  end
@@ -2,13 +2,22 @@
2
2
  require 'singleton'
3
3
 
4
4
  module Synvert
5
+ # Synvert global configuration.
5
6
  class Configuration < Hash
6
7
  include Singleton
7
8
 
9
+ # Set the configuration.
10
+ #
11
+ # @param key [String] configuration key.
12
+ # @param value [Object] configuration value.
8
13
  def set(key, value)
9
14
  self[key] = value
10
15
  end
11
16
 
17
+ # Get the configuration.
18
+ #
19
+ # @param key [String] configuration key.
20
+ # @return [Object] configuration value.
12
21
  def get(key)
13
22
  self[key]
14
23
  end
@@ -0,0 +1,13 @@
1
+ module Synvert
2
+ # Rewriter not found exception.
3
+ class RewriterNotFound < Exception
4
+ end
5
+
6
+ # Gemfile.lock not found exception.
7
+ class GemfileLockNotFound < Exception
8
+ end
9
+
10
+ # Method not supported exception.
11
+ class MethodNotSupported < Exception
12
+ end
13
+ end
@@ -1,4 +1,9 @@
1
+ # Parser::AST::Node monkey patch.
1
2
  class Parser::AST::Node
3
+ # Get name node of :class, :module, :def and :defs node.
4
+ #
5
+ # @return [Parser::AST::Node] name node.
6
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
2
7
  def name
3
8
  case self.type
4
9
  when :class, :module, :def
@@ -6,26 +11,38 @@ class Parser::AST::Node
6
11
  when :defs
7
12
  self.children[1]
8
13
  else
9
- raise NotImplementedError.new "name is not handled for #{self.inspect}"
14
+ raise Synvert::MethodNotSupported.new "name is not handled for #{self.inspect}"
10
15
  end
11
16
  end
12
17
 
18
+ # Get receiver node of :send node.
19
+ #
20
+ # @return [Parser::AST::Node] receiver node.
21
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
13
22
  def receiver
14
23
  if :send == self.type
15
24
  self.children[0]
16
25
  else
17
- raise NotImplementedError.new "receiver is not handled for #{self.inspect}"
26
+ raise Synvert::MethodNotSupported.new "receiver is not handled for #{self.inspect}"
18
27
  end
19
28
  end
20
29
 
30
+ # Get message node of :send node.
31
+ #
32
+ # @return [Parser::AST::Node] mesage node.
33
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
21
34
  def message
22
35
  if :send == self.type
23
36
  self.children[1]
24
37
  else
25
- raise NotImplementedError.new "message is not handled for #{self.inspect}"
38
+ raise Synvert::MethodNotSupported.new "message is not handled for #{self.inspect}"
26
39
  end
27
40
  end
28
41
 
42
+ # Get arguments node of :send, :block or :defined? node.
43
+ #
44
+ # @return [Array<Parser::AST::Node>] arguments node.
45
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
29
46
  def arguments
30
47
  case self.type
31
48
  when :send
@@ -35,18 +52,26 @@ class Parser::AST::Node
35
52
  when :defined?
36
53
  self.children
37
54
  else
38
- raise NotImplementedError.new "arguments is not handled for #{self.inspect}"
55
+ raise Synvert::MethodNotSupported.new "arguments is not handled for #{self.inspect}"
39
56
  end
40
57
  end
41
58
 
59
+ # Get caller node of :block node.
60
+ #
61
+ # @return [Parser::AST::Node] caller node.
62
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
42
63
  def caller
43
64
  if :block == self.type
44
65
  self.children[0]
45
66
  else
46
- raise NotImplementedError.new "caller is not handled for #{self.inspect}"
67
+ raise Synvert::MethodNotSupported.new "caller is not handled for #{self.inspect}"
47
68
  end
48
69
  end
49
70
 
71
+ # Get body node of :begin or :block node.
72
+ #
73
+ # @return [Array<Parser::AST::Node>] body node.
74
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
50
75
  def body
51
76
  case self.type
52
77
  when :begin
@@ -54,60 +79,91 @@ class Parser::AST::Node
54
79
  when :block
55
80
  :begin == self.children[2].type ? self.children[2].children : [self.children[2]]
56
81
  else
57
- raise NotImplementedError.new "body is not handled for #{self.inspect}"
82
+ raise Synvert::MethodNotSupported.new "body is not handled for #{self.inspect}"
58
83
  end
59
84
  end
60
85
 
86
+ # Get condition node of :if node.
87
+ #
88
+ # @return [Parser::AST::Node] condition node.
89
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
61
90
  def condition
62
91
  if :if == self.type
63
92
  self.children[0]
64
93
  else
65
- raise NotImplementedError.new "condition is not handled for #{self.inspect}"
94
+ raise Synvert::MethodNotSupported.new "condition is not handled for #{self.inspect}"
66
95
  end
67
96
  end
68
97
 
98
+ # Get keys node of :hash node.
99
+ #
100
+ # @return [Array<Parser::AST::Node>] keys node.
101
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
69
102
  def keys
70
103
  if :hash == self.type
71
104
  self.children.map { |child| child.children[0] }
72
105
  else
73
- raise NotImplementedError.new "keys is not handled for #{self.inspect}"
106
+ raise Synvert::MethodNotSupported.new "keys is not handled for #{self.inspect}"
74
107
  end
75
108
  end
76
109
 
110
+ # Get values node of :hash node.
111
+ #
112
+ # @return [Array<Parser::AST::Node>] values node.
113
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
77
114
  def values
78
115
  if :hash == self.type
79
116
  self.children.map { |child| child.children[1] }
80
117
  else
81
- raise NotImplementedError.new "keys is not handled for #{self.inspect}"
118
+ raise Synvert::MethodNotSupported.new "keys is not handled for #{self.inspect}"
82
119
  end
83
120
  end
84
121
 
122
+ # Get key node of hash :pair node.
123
+ #
124
+ # @return [Parser::AST::Node] key node.
125
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
85
126
  def key
86
127
  if :pair == self.type
87
128
  self.children.first
88
129
  else
89
- raise NotImplementedError.new "key is not handled for #{self.inspect}"
130
+ raise Synvert::MethodNotSupported.new "key is not handled for #{self.inspect}"
90
131
  end
91
132
  end
92
133
 
134
+ # Get value node of hash :pair node.
135
+ #
136
+ # @return [Parser::AST::Node] value node.
137
+ # @raise [Synvert::MethodNotSupported] if calls on other node.
93
138
  def value
94
139
  if :pair == self.type
95
140
  self.children.last
96
141
  else
97
- raise NotImplementedError.new "value is not handled for #{self.inspect}"
142
+ raise Synvert::MethodNotSupported.new "value is not handled for #{self.inspect}"
98
143
  end
99
144
  end
100
145
 
146
+ # Get the source code of current node.
147
+ #
148
+ # @param instance [Synvert::Rewriter::Instance]
149
+ # @return [String] source code.
101
150
  def source(instance)
102
151
  if self.loc.expression
103
152
  instance.current_source[self.loc.expression.begin_pos...self.loc.expression.end_pos]
104
153
  end
105
154
  end
106
155
 
156
+ # Get the indent of current node.
157
+ #
158
+ # @return [Integer] indent.
107
159
  def indent
108
160
  self.loc.expression.column
109
161
  end
110
162
 
163
+ # Recursively iterate all child nodes of current node.
164
+ #
165
+ # @yield [child] Gives a child node.
166
+ # @yieldparam child [Parser::AST::Node] child node
111
167
  def recursive_children
112
168
  self.children.each do |child|
113
169
  if Parser::AST::Node === child
@@ -117,6 +173,11 @@ class Parser::AST::Node
117
173
  end
118
174
  end
119
175
 
176
+ # Match current node with rules.
177
+ #
178
+ # @param instance [Synvert::Rewriter::Instance] used to get crrent source code.
179
+ # @param rules [Hash] rules to match.
180
+ # @return true if matches.
120
181
  def match?(instance, rules)
121
182
  flat_hash(rules).keys.all? do |multi_keys|
122
183
  if multi_keys.last == :any
@@ -135,6 +196,13 @@ class Parser::AST::Node
135
196
  end
136
197
  end
137
198
 
199
+ # Get rewritten source code.
200
+ # @example
201
+ # node.rewritten_source("create({{arguments}})") #=> "create(:post)"
202
+ #
203
+ # @param code [String] raw code.
204
+ # @return [String] rewritten code, replace string in block {{ }} in raw code.
205
+ # @raise [Synvert::MethodNotSupported] if string in block {{ }} does not support.
138
206
  def rewritten_source(code)
139
207
  code.gsub(/{{(.*?)}}/m) do
140
208
  evaluated = self.instance_eval $1
@@ -152,13 +220,20 @@ class Parser::AST::Node
152
220
  when NilClass
153
221
  'nil'
154
222
  else
155
- raise NotImplementedError.new "rewritten_source is not handled for #{evaluated.inspect}"
223
+ raise Synvert::MethodNotSupported.new "rewritten_source is not handled for #{evaluated.inspect}"
156
224
  end
157
225
  end
158
226
  end
159
227
 
160
228
  private
161
229
 
230
+ # Compare actual value with expected value.
231
+ #
232
+ # @param instance [Synvert::Rewriter::Instance] used to get source code.
233
+ # @param actual [Object] actual value.
234
+ # @param expected [Object] expected value.
235
+ # @return [Integer] -1, 0 or 1.
236
+ # @raise [Synvert::MethodNotSupported] if expected class is not supported.
162
237
  def match_value?(instance, actual, expected)
163
238
  case expected
164
239
  when Symbol
@@ -196,10 +271,17 @@ private
196
271
  when Parser::AST::Node
197
272
  actual == expected
198
273
  else
199
- raise NotImplementedError.new "#{expected.class} is not handled for match_value?"
274
+ raise Synvert::MethodNotSupported.new "#{expected.class} is not handled for match_value?"
200
275
  end
201
276
  end
202
277
 
278
+ # Convert a hash to flat one.
279
+ #
280
+ # @example
281
+ # flat_hash(type: 'block', caller: {type: 'send', receiver: 'RSpec'})
282
+ # #=> {[:type] => 'block', [:caller, :type] => 'send', [:caller, :receiver] => 'RSpec'}
283
+ # @param h [Hash] original hash.
284
+ # @return flatten hash.
203
285
  def flat_hash(h, k = [])
204
286
  new_hash = {}
205
287
  h.each_pair do |key, val|
@@ -212,6 +294,12 @@ private
212
294
  new_hash
213
295
  end
214
296
 
297
+ # Get actual value from the node.
298
+ #
299
+ # @param node [Parser::AST::Node]
300
+ # @param instance [Synvert::Rewriter::Instance]
301
+ # @param multi_keys [Array<Symbol>]
302
+ # @return [Object] actual value.
215
303
  def actual_value(node, instance, multi_keys)
216
304
  multi_keys.inject(node) { |n, key|
217
305
  if n
@@ -220,6 +308,11 @@ private
220
308
  }
221
309
  end
222
310
 
311
+ # Get expected value from rules.
312
+ #
313
+ # @param rules [Hash]
314
+ # @param multi_keys [Array<Symbol>]
315
+ # @return [Object] expected value.
223
316
  def expected_value(rules, multi_keys)
224
317
  multi_keys.inject(rules) { |o, key| o[key] }
225
318
  end