synvert 0.0.13 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
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