rails_best_practices 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/Gemfile.lock +2 -2
  2. data/README.md +1 -0
  3. data/assets/result.html.erb +24 -6
  4. data/lib/rails_best_practices.rb +19 -3
  5. data/lib/rails_best_practices/command.rb +4 -0
  6. data/lib/rails_best_practices/core/error.rb +1 -0
  7. data/lib/rails_best_practices/core/methods.rb +14 -2
  8. data/lib/rails_best_practices/core/runner.rb +2 -2
  9. data/lib/rails_best_practices/core_ext/sexp.rb +47 -2
  10. data/lib/rails_best_practices/prepares/controller_prepare.rb +9 -1
  11. data/lib/rails_best_practices/prepares/model_prepare.rb +15 -3
  12. data/lib/rails_best_practices/reviews/remove_unused_methods_in_models_review.rb +58 -5
  13. data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +32 -24
  14. data/lib/rails_best_practices/reviews/review.rb +2 -1
  15. data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +3 -1
  16. data/lib/rails_best_practices/reviews/use_before_filter_review.rb +1 -1
  17. data/lib/rails_best_practices/version.rb +1 -2
  18. data/rails_best_practices.yml +2 -2
  19. data/rake_rubies.sh +3 -4
  20. data/spec/rails_best_practices/core/runner_spec.rb +14 -5
  21. data/spec/rails_best_practices/core_ext/sexp_spec.rb +45 -0
  22. data/spec/rails_best_practices/prepares/controller_prepare_spec.rb +11 -0
  23. data/spec/rails_best_practices/prepares/model_prepare_spec.rb +35 -0
  24. data/spec/rails_best_practices/reviews/move_code_into_model_review_spec.rb +11 -2
  25. data/spec/rails_best_practices/reviews/remove_unused_methods_in_models_review_spec.rb +166 -0
  26. data/spec/rails_best_practices/reviews/restrict_auto_generated_routes_review_spec.rb +20 -0
  27. data/spec/rails_best_practices/reviews/simplify_render_in_views_review_spec.rb +11 -3
  28. data/spec/spec_helper.rb +2 -0
  29. metadata +3 -3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_best_practices (1.2.0)
4
+ rails_best_practices (1.3.0)
5
5
  activesupport
6
6
  colored
7
7
  erubis
@@ -12,7 +12,7 @@ PATH
12
12
  GEM
13
13
  remote: http://rubygems.org/
14
14
  specs:
15
- activesupport (3.0.7)
15
+ activesupport (3.0.10)
16
16
  colored (1.2)
17
17
  diff-lcs (1.1.2)
18
18
  erubis (2.7.0)
data/README.md CHANGED
@@ -23,6 +23,7 @@ By default rails_best_practices will do parse codes in vendor, spec, test and fe
23
23
  --without-color only output plain text without color
24
24
  --with-textmate open file by textmate in html format
25
25
  --with-mvim open file by mvim in html format
26
+ --with-git display git commit and git username in html format
26
27
  --vendor include vendor files
27
28
  --spec include spec files
28
29
  --test include test files
@@ -12,11 +12,14 @@
12
12
  h1 {
13
13
  color: ##4E4E4E;
14
14
  }
15
+ p {
16
+ margin: 5px 0;
17
+ }
15
18
  table {
16
19
  background: white;
17
20
  border: 1px solid #666;
18
21
  border-collapse: collapse;
19
- margin: 20px 0;
22
+ margin: 10px 0;
20
23
  font-size: 14px;
21
24
  }
22
25
  table th, table td {
@@ -33,26 +36,33 @@
33
36
  table tr:hover {
34
37
  background-color: #FFFFC0;
35
38
  }
39
+ ul {
40
+ clear: both;
41
+ display: inline-block;
42
+ padding: 0;
43
+ margin: 0;
44
+ }
36
45
  ul li {
37
46
  list-style: none;
38
47
  display: none;
48
+ float: left;
39
49
  }
40
50
  </style>
41
51
  </head>
42
52
  <body>
43
53
  <h1>rails_best_practices output</h1>
44
- <h2>
54
+ <p>
45
55
  Please go to
46
56
  <a href='http://rails-bestpractices.com' target='_blank'>http://rails-bestpractices.com</a>
47
57
  to see more useful Rails Best Practices.
48
- </h2>
49
- <h2>
58
+ </p>
59
+ <p>
50
60
  <% if @errors.empty? %>
51
61
  No error found. Cool!
52
62
  <% else %>
53
- Found <%= @errors.size %> errors.
63
+ Found <%= @errors.size %> warnings.
54
64
  <% end %>
55
- </h2>
65
+ </p>
56
66
  <ul>
57
67
  <% @error_types.each do |error_type| %>
58
68
  <li><input type="checkbox" value="<%= error_type.split(':').last %>" /><%= error_type.split(':').last %></li>
@@ -63,6 +73,10 @@
63
73
  <th>Filename</th>
64
74
  <th>Line Number</th>
65
75
  <th>Warning Message</th>
76
+ <% if @git %>
77
+ <th>Git Commit</th>
78
+ <th>Git Username</th>
79
+ <% end %>
66
80
  </tr>
67
81
  <% @errors.each do |error| %>
68
82
  <tr class="<%= error.type.split(':').last %>">
@@ -79,6 +93,10 @@
79
93
  <td class='message'>
80
94
  <a href='<%= error.url %>' target='_blank'><%= error.message %></a>
81
95
  </td>
96
+ <% if @git %>
97
+ <td class='git_commit'><%= error.git_commit %></td>
98
+ <td class='git_usename'><%= error.git_username %></td>
99
+ <% end %>
82
100
  </tr>
83
101
  <% end %>
84
102
  </table>
@@ -81,12 +81,13 @@ module RailsBestPractices
81
81
  plain_output("AlwaysAddDbIndexReview is disabled as there is no db/schema.rb file in your rails project.", 'blue')
82
82
  end
83
83
 
84
- @bar = ProgressBar.new('Analyzing', lexical_files.size + prepare_files.size + review_files.size)
84
+ @bar = ProgressBar.new('Source Codes', lexical_files.size + prepare_files.size + review_files.size)
85
85
  ["lexical", "prepare", "review"].each { |process| send(:process, process) }
86
86
  @runner.on_complete
87
87
  @bar.finish
88
88
 
89
89
  if @options['format'] == 'html'
90
+ load_git_info if @options["with-git"]
90
91
  output_html_errors
91
92
  else
92
93
  output_terminal_errors
@@ -148,7 +149,7 @@ module RailsBestPractices
148
149
  # @param [Array] dirs what directories to expand
149
150
  # @return [Array] all files expanded
150
151
  def expand_dirs_to_files *dirs
151
- extensions = ['rb', 'erb', 'rhtml', 'haml', 'builder']
152
+ extensions = ['rb', 'erb', 'rake', 'rhtml', 'haml', 'builder']
152
153
 
153
154
  dirs.flatten.map { |entry|
154
155
  next unless File.exist? entry
@@ -214,6 +215,21 @@ module RailsBestPractices
214
215
  end
215
216
  end
216
217
 
218
+ # load git commit and git username info.
219
+ def load_git_info
220
+ git_progressbar = ProgressBar.new('Git Info', @runner.errors.size)
221
+ @runner.errors.each do |error|
222
+ git_info = `cd #{@runner.class.base_path}; git blame #{error.filename[@runner.class.base_path.size..-1]} | sed -n #{error.line_number.split(',').first}p`
223
+ unless git_info == ""
224
+ git_commit, git_username = git_info.split(/\d{4}-\d{2}-\d{2}/).first.split("(")
225
+ error.git_commit = git_commit.split(" ").first.strip
226
+ error.git_username = git_username.strip
227
+ end
228
+ git_progressbar.inc unless @options['debug']
229
+ end
230
+ git_progressbar.finish
231
+ end
232
+
217
233
  # output errors with html format.
218
234
  def output_html_errors
219
235
  require 'erubis'
@@ -221,7 +237,7 @@ module RailsBestPractices
221
237
 
222
238
  File.open("rails_best_practices_output.html", "w+") do |file|
223
239
  eruby = Erubis::Eruby.new(template)
224
- file.puts eruby.evaluate(:errors => @runner.errors, :error_types => error_types, :textmate => @options["with-textmate"], :mvim => @options["with-mvim"])
240
+ file.puts eruby.evaluate(:errors => @runner.errors, :error_types => error_types, :textmate => @options["with-textmate"], :mvim => @options["with-mvim"], :git => @options["with-git"])
225
241
  end
226
242
  end
227
243
 
@@ -40,6 +40,10 @@ OptionParser.new do |opts|
40
40
  options["with-mvim"] = true
41
41
  end
42
42
 
43
+ opts.on("--with-git", "display git commit and username, only support html format") do
44
+ options["with-git"] = true
45
+ end
46
+
43
47
  ['vendor', 'spec', 'test', 'features'].each do |pattern|
44
48
  opts.on("--#{pattern}", "include #{pattern} files") do
45
49
  options[pattern] = true
@@ -6,6 +6,7 @@ module RailsBestPractices
6
6
  # it indicates the filenname, line number and error message for the violation.
7
7
  class Error
8
8
  attr_reader :filename, :line_number, :message, :type, :url
9
+ attr_accessor :git_commit, :git_username
9
10
 
10
11
  def initialize(filename, line_number, message, type, url = nil)
11
12
  @filename = filename
@@ -53,15 +53,27 @@ module RailsBestPractices
53
53
  #
54
54
  # @param [String] class name
55
55
  # @param [String] method name
56
- def mark_extend_class_method_used(class_name, method_name)
56
+ def mark_parent_class_method_used(class_name, method_name)
57
57
  klass = Prepares.klasses.find { |klass| klass.to_s == class_name }
58
58
  if klass && klass.extend_class_name
59
- mark_extend_class_method_used(klass.extend_class_name, method_name)
59
+ mark_parent_class_method_used(klass.extend_class_name, method_name)
60
60
  method = get_method(klass.extend_class_name, method_name)
61
61
  method.mark_used if method
62
62
  end
63
63
  end
64
64
 
65
+ # Mark sub classes' method as used.
66
+ #
67
+ # @param [String] class name
68
+ # @param [String] method name
69
+ def mark_subclasses_method_used(class_name, method_name)
70
+ Prepares.klasses.select { |klass| klass.extend_class_name == class_name }.each do |klass|
71
+ mark_subclasses_method_used(klass.to_s, method_name)
72
+ method = get_method(klass.to_s, method_name)
73
+ method.mark_used if method
74
+ end
75
+ end
76
+
65
77
  # remomber the method name, the method is probably be used for the class' public method.
66
78
  #
67
79
  # @param [String] method name
@@ -159,7 +159,7 @@ module RailsBestPractices
159
159
  content.gsub!(/\\\d{3}/, '')
160
160
  rescue LoadError
161
161
  raise "In order to parse #{filename}, please install the haml gem"
162
- rescue Haml::Error
162
+ rescue Haml::Error, SyntaxError
163
163
  # do nothing, just ignore the wrong haml files.
164
164
  end
165
165
  end
@@ -202,7 +202,7 @@ module RailsBestPractices
202
202
  # load all plugin reviews.
203
203
  def load_plugin_reviews
204
204
  begin
205
- plugins = "#{Runner.base_path}lib/rails_best_practices/plugins/reviews"
205
+ plugins = File.join(Runner.base_path, 'lib', 'rails_best_practices', 'plugins', 'reviews')
206
206
  if File.directory?(plugins)
207
207
  Dir[File.expand_path(File.join(plugins, "*.rb"))].each do |review|
208
208
  require review
@@ -22,8 +22,11 @@ class Sexp
22
22
  # => 2
23
23
  def line
24
24
  if [:def, :command, :command_call, :call, :fcall, :method_add_arg, :method_add_block,
25
- :var_ref, :const_ref, :const_path_ref, :class, :module, :if, :unless, :elsif, :binary].include? sexp_type
25
+ :var_ref, :const_ref, :const_path_ref, :class, :module, :if, :unless, :elsif, :binary,
26
+ :alias, :symbol_literal, :symbol, :aref].include? sexp_type
26
27
  self[1].line
28
+ elsif :array == sexp_type
29
+ array_values[0].line
27
30
  else
28
31
  self.last.first if self.last.is_a? Array
29
32
  end
@@ -294,7 +297,7 @@ class Sexp
294
297
  when :args_add_block, :array
295
298
  node = self[1]
296
299
  while true
297
- if :args_add == node.sexp_type
300
+ if [:args_add, :args_add_star].include? node.sexp_type
298
301
  nodes.unshift node[2]
299
302
  node = node[1]
300
303
  elsif :args_new == node.sexp_type
@@ -675,12 +678,48 @@ class Sexp
675
678
  if :array == sexp_type
676
679
  if nil == self[1]
677
680
  []
681
+ elsif :qwords_add == self[1].sexp_type
682
+ self[1].array_values
678
683
  else
679
684
  arguments.all
680
685
  end
686
+ elsif :qwords_add
687
+ values = []
688
+ node = self
689
+ while true
690
+ if :qwords_add == node.sexp_type
691
+ values.unshift node[2]
692
+ node = node[1]
693
+ elsif :qwords_new == node.sexp_type
694
+ break
695
+ end
696
+ end
697
+ values
681
698
  end
682
699
  end
683
700
 
701
+ # old method for alias node.
702
+ #
703
+ # s(:alias,
704
+ # s(:symbol_literal, s(:@ident, "new", s(1, 6))),
705
+ # s(:symbol_literal, s(:@ident, "old", s(1, 10)))
706
+ # )
707
+ # => s(:symbol_literal, s(:@ident, "old", s(1, 10))),
708
+ def old_method
709
+ self[2]
710
+ end
711
+
712
+ # new method for alias node.
713
+ #
714
+ # s(:alias,
715
+ # s(:symbol_literal, s(:@ident, "new", s(1, 6))),
716
+ # s(:symbol_literal, s(:@ident, "old", s(1, 10)))
717
+ # )
718
+ # => s(:symbol_literal, s(:@ident, "new", s(1, 6))),
719
+ def new_method
720
+ self[1]
721
+ end
722
+
684
723
  # To object.
685
724
  #
686
725
  # s(:array,
@@ -726,6 +765,12 @@ class Sexp
726
765
  else
727
766
  self[1].to_s
728
767
  end
768
+ when :qwords_add
769
+ if s(:qwords_new) == self[1]
770
+ self[2].to_s
771
+ else
772
+ self[1].to_s
773
+ end
729
774
  when :const_path_ref
730
775
  "#{self[1]}::#{self[2]}"
731
776
  when :@label
@@ -54,7 +54,15 @@ module RailsBestPractices
54
54
  # restrict actions for inherited_resources
55
55
  def start_command(node)
56
56
  if @inherited_resources && "actions" == node.message.to_s
57
- @actions = node.arguments.all.map(&:to_s)
57
+ if "all" == node.arguments.all[0].to_s
58
+ @actions = DEFAULT_ACTIONS
59
+ option_argument = node.arguments.all[1]
60
+ if :bare_assoc_hash == option_argument.sexp_type && option_argument.hash_value("except")
61
+ @actions -= option_argument.hash_value("except").to_object
62
+ end
63
+ else
64
+ @actions = node.arguments.all.map(&:to_s)
65
+ end
58
66
  end
59
67
  end
60
68
 
@@ -11,7 +11,7 @@ module RailsBestPractices
11
11
  ASSOCIATION_METHODS = %w(belongs_to has_one has_many has_and_belongs_to_many)
12
12
 
13
13
  def interesting_nodes
14
- [:module, :class, :def, :command, :var_ref]
14
+ [:module, :class, :def, :command, :var_ref, :alias]
15
15
  end
16
16
 
17
17
  def interesting_files
@@ -62,14 +62,26 @@ module RailsBestPractices
62
62
  # }
63
63
  # }
64
64
  def start_command(node)
65
- if %w(named_scope scope).include? node.message.to_s
65
+ case node.message.to_s
66
+ when *%w(named_scope scope alias_method)
66
67
  method_name = node.arguments.all[0].to_s
67
68
  @methods.add_method(current_class_name, method_name, {"file" => node.file, "line" => node.line}, current_access_control)
68
- elsif ASSOCIATION_METHODS.include? node.message.to_s
69
+ when "alias_method_chain"
70
+ method, feature = *node.arguments.all.map(&:to_s)
71
+ @methods.add_method(current_class_name, "#{method}_with_#{feature}", {"file" => node.file, "line" => node.line}, current_access_control)
72
+ @methods.add_method(current_class_name, "#{method}", {"file" => node.file, "line" => node.line}, current_access_control)
73
+ when *ASSOCIATION_METHODS
69
74
  remember_association(node)
75
+ else
70
76
  end
71
77
  end
72
78
 
79
+ # check alias node to remembr the alias methods.
80
+ def start_alias(node)
81
+ method_name = node.new_method.to_s
82
+ @methods.add_method(current_class_name, method_name, {"file" => node.file, "line" => node.line}, current_access_control)
83
+ end
84
+
73
85
  private
74
86
  # remember associations, with class to association names.
75
87
  def remember_association(node)
@@ -3,14 +3,22 @@ require 'rails_best_practices/reviews/review'
3
3
 
4
4
  module RailsBestPractices
5
5
  module Reviews
6
+ # Find out unused methods in models.
7
+ #
8
+ # Implemenation:
9
+ #
10
+ # Review process:
11
+ # remember all method calls,
12
+ # at end, check if all defined methods are called,
13
+ # if not, non called methods are unused.
6
14
  class RemoveUnusedMethodsInModelsReview < Review
7
15
  include Klassable
8
16
  include Completeable
9
17
 
10
- EXCEPT_METHODS = %w(initialize validate to_xml to_json)
18
+ EXCEPT_METHODS = %w(initialize validate to_xml to_json assign_attributes after_find after_initialize)
11
19
 
12
20
  def interesting_nodes
13
- [:module, :class, :call, :fcall, :command, :method_add_arg, :var_ref]
21
+ [:module, :class, :call, :fcall, :command, :command_call, :method_add_arg, :var_ref, :alias, :bare_assoc_hash]
14
22
  end
15
23
 
16
24
  def initialize(options={})
@@ -19,32 +27,76 @@ module RailsBestPractices
19
27
  @except_methods = EXCEPT_METHODS + options['except_methods']
20
28
  end
21
29
 
30
+ # remember the message of call node.
22
31
  def start_call(node)
23
32
  mark_used(node.message)
24
33
  end
25
34
 
35
+ # remember the message of fcall node.
26
36
  def start_fcall(node)
27
37
  mark_used(node.message)
28
38
  end
29
39
 
40
+ # remember name of var_ref node.
30
41
  def start_var_ref(node)
31
42
  mark_used(node)
32
43
  end
33
44
 
45
+ # remember the message of command call node.
46
+ def start_command_call(node)
47
+ mark_used(node.message)
48
+ end
49
+
50
+ # remember the message of command node.
51
+ # remember the argument of alias_method and alias_method_chain as well.
34
52
  def start_command(node)
35
- unless %w(named_scope scope).include? node.message.to_s
53
+ case node.message.to_s
54
+ when "named_scope", "scope"
55
+ # nothing
56
+ when "alias_method"
57
+ mark_used(node.arguments.all[1])
58
+ when "alias_method_chain"
59
+ method, feature = *node.arguments.all.map(&:to_s)
60
+ call_method("#{method}_with_#{feature}")
61
+ else
36
62
  mark_used(node.message)
37
63
  node.arguments.all.each { |argument| mark_used(argument) }
38
64
  end
39
65
  end
40
66
 
67
+ # remember the old method of alias node.
68
+ def start_alias(node)
69
+ mark_used(node.old_method)
70
+ end
71
+
72
+ # remember hash values for hash key "methods".
73
+ #
74
+ # def to_xml(options = {})
75
+ # super options.merge(:exclude => :visible, :methods => [:is_discussion_conversation])
76
+ # end
77
+ def start_bare_assoc_hash(node)
78
+ if node.hash_keys.include? "methods"
79
+ mark_used(node.hash_value("methods"))
80
+ end
81
+ end
82
+
83
+ # remember the first argument for try and send method.
41
84
  def start_method_add_arg(node)
42
- if "try" == node.message.to_s
85
+ case node.message.to_s
86
+ when "try"
43
87
  method_name = node.arguments.all[0].to_s
44
88
  call_method(method_name)
89
+ when "send"
90
+ if [:symbol_literal, :string_literal].include?(node.arguments.all[0].sexp_type)
91
+ method_name = node.arguments.all[0].to_s
92
+ call_method(method_name)
93
+ end
94
+ else
95
+ # nothing
45
96
  end
46
97
  end
47
98
 
99
+ # get all unused methods at the end of review process.
48
100
  def on_complete
49
101
  @model_methods.get_all_unused_methods.each do |method|
50
102
  if !@except_methods.include?(method.method_name) && method.method_name !~ /=$/
@@ -69,7 +121,8 @@ module RailsBestPractices
69
121
  if @model_methods.has_method?(current_class_name, method_name)
70
122
  @model_methods.get_method(current_class_name, method_name).mark_used
71
123
  end
72
- @model_methods.mark_extend_class_method_used(current_class_name, method_name)
124
+ @model_methods.mark_parent_class_method_used(current_class_name, method_name)
125
+ @model_methods.mark_subclasses_method_used(current_class_name, method_name)
73
126
  @model_methods.possible_public_used(method_name)
74
127
  end
75
128
  end
@@ -86,10 +86,10 @@ module RailsBestPractices
86
86
 
87
87
  # get the controller name.
88
88
  def controller_name(node)
89
- if node.arguments.all.size > 1
90
- options = node.arguments.all[1]
91
- if options.hash_keys.include?("controller")
92
- name = options.hash_value("controller").to_s
89
+ if option_with_hash(node)
90
+ option_node = node.arguments.all[1]
91
+ if hash_key_exist?(option_node,"controller")
92
+ name = option_node.hash_value("controller").to_s
93
93
  else
94
94
  name = node.arguments.all[0].to_s.tableize
95
95
  end
@@ -111,39 +111,47 @@ module RailsBestPractices
111
111
 
112
112
  # get the route actions that should be generated by resources call.
113
113
  def resources_methods(node)
114
- resources_methods = RESOURCES_METHODS
115
-
116
- if node.arguments.all.size > 1
117
- options = node.arguments.all[1]
118
- if options.hash_keys.include?("only")
119
- Array(options.hash_value("only").to_object)
120
- elsif options.hash_keys.include?("except")
121
- resources_methods - Array(options.hash_value("except").to_object)
114
+ methods = RESOURCES_METHODS
115
+
116
+ if option_with_hash(node)
117
+ option_node = node.arguments.all[1]
118
+ if hash_key_exist?(option_node, "only")
119
+ option_node.hash_value("only").to_s == "none" ? [] : Array(option_node.hash_value("only").to_object)
120
+ elsif hash_key_exist?(option_node, "except")
121
+ option_node.hash_value("except").to_s == "all" ? [] : (methods - Array(option_node.hash_value("except").to_object))
122
122
  else
123
- resources_methods
123
+ methods
124
124
  end
125
125
  else
126
- resources_methods
126
+ methods
127
127
  end
128
128
  end
129
129
 
130
130
  # get the route actions that should be generated by resource call.
131
131
  def resource_methods(node)
132
- resource_methods = RESOURCE_METHODS
133
-
134
- if node.arguments.all.size > 1
135
- options = node.arguments.all[1]
136
- if options.hash_keys.include?("only")
137
- Array(options.hash_value("only").to_object)
138
- elsif options.hash_keys.include?("except")
139
- resource_methods - Array(options.hash_value("except").to_object)
132
+ methods = RESOURCE_METHODS
133
+
134
+ if option_with_hash(node)
135
+ option_node = node.arguments.all[1]
136
+ if hash_key_exist?(option_node, "only")
137
+ option_node.hash_value("only").to_s == "none" ? [] : Array(option_node.hash_value("only").to_object)
138
+ elsif hash_key_exist?(option_node, "except")
139
+ option_node.hash_value("except").to_s == "all" ? [] : (methods - Array(option_node.hash_value("except").to_object))
140
140
  else
141
- resource_methods
141
+ methods
142
142
  end
143
143
  else
144
- resource_methods
144
+ methods
145
145
  end
146
146
  end
147
+
148
+ def option_with_hash(node)
149
+ node.arguments.all.size > 1 && :bare_assoc_hash == node.arguments.all[1].sexp_type
150
+ end
151
+
152
+ def hash_key_exist?(node, key)
153
+ node.hash_keys && node.hash_keys.include?(key)
154
+ end
147
155
  end
148
156
  end
149
157
  end
@@ -16,7 +16,8 @@ module RailsBestPractices
16
16
  # then save it to as key in @variable_use_count hash, and add the call count (hash value).
17
17
  def remember_variable_use_count(node)
18
18
  variable_node = variable(node)
19
- if variable_node && "self" != variable_node.to_s
19
+ if variable_node && "self" != variable_node.to_s && @last_variable_node != variable_node
20
+ @last_variable_node = variable_node
20
21
  variable_use_count[variable_node.to_s] ||= 0
21
22
  variable_use_count[variable_node.to_s] += 1
22
23
  end
@@ -31,7 +31,9 @@ module RailsBestPractices
31
31
  def start_command(node)
32
32
  if "render" == node.message.to_s
33
33
  hash_node = node.arguments.all[0]
34
- if hash_node && hash_node.hash_keys && hash_node.hash_keys.include?("partial")
34
+ if hash_node && :bare_assoc_hash == hash_node.sexp_type &&
35
+ hash_node.hash_keys.include?("partial") &&
36
+ !hash_node.hash_value("partial").to_s.include?('/')
35
37
  add_error 'simplify render in views'
36
38
  end
37
39
  end
@@ -27,7 +27,7 @@ module RailsBestPractices
27
27
 
28
28
  def initialize(options = {})
29
29
  super()
30
- @customize_count = options['customize_count'] || 1
30
+ @customize_count = options['customize_count'] || 2
31
31
  end
32
32
 
33
33
  # check class define node to see if there are method define nodes whose first code line are duplicated.
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module RailsBestPractices
3
- VERSION = "1.2.0"
3
+ VERSION = "1.3.0"
4
4
  end
5
-
@@ -12,7 +12,7 @@ LawOfDemeterCheck: { }
12
12
  UseObserverCheck: { }
13
13
  IsolateSeedDataCheck: { }
14
14
  AlwaysAddDbIndexCheck: { }
15
- UseBeforeFilterCheck: { customize_count: 1 }
15
+ UseBeforeFilterCheck: { customize_count: 2 }
16
16
  MoveCodeIntoControllerCheck: { }
17
17
  MoveCodeIntoModelCheck: { use_count: 2 }
18
18
  MoveCodeIntoHelperCheck: { array_count: 3 }
@@ -27,4 +27,4 @@ SimplifyRenderInControllersCheck: { }
27
27
  RemoveEmptyHelpersCheck: { }
28
28
  RemoveTabCheck: { }
29
29
  RestrictAutoGeneratedRoutesCheck: { }
30
- # RemoveUnusedMethodsInModelsCheck: { except_methods: [] }
30
+ RemoveUnusedMethodsInModelsCheck: { except_methods: [] }
data/rake_rubies.sh CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/bin/bash
2
- rubies=( 1.8.7 1.9.2 ree )
2
+ rubies=( 1.9.2-p180 1.9.2-p290 1.9.3-p0 )
3
3
  gemset="rails_best_practices"
4
4
 
5
5
  for x in ${rubies[*]}
6
6
  do
7
- rvm use --create $x@$gemset
8
- bundle install
9
- rake spec:progress
7
+ echo $x@$gemset
8
+ rvm $x@$gemset do bundle exec rake spec:progress
10
9
  done
@@ -1,13 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe RailsBestPractices::Core::Runner do
4
+ describe "load_plugin_reviews" do
5
+ shared_examples_for 'load_plugin_reviews' do
6
+ it "should load plugins in lib/rails_best_practices/plugins/reviews" do
7
+ runner = RailsBestPractices::Core::Runner.new
8
+ runner.instance_variable_get('@reviews').map(&:class).should include(RailsBestPractices::Plugins::Reviews::NotUseRailsRootReview)
9
+ end
10
+ end
4
11
 
5
- before { RailsBestPractices::Core::Runner.base_path = 'spec/fixtures/' }
12
+ context "given a path that ends with a slash" do
13
+ before { RailsBestPractices::Core::Runner.base_path = 'spec/fixtures/' }
14
+ it_should_behave_like 'load_plugin_reviews'
15
+ end
6
16
 
7
- describe "load_plugin_reviews" do
8
- it "should load plugins in lib/rails_best_practices/plugins/reviews" do
9
- runner = RailsBestPractices::Core::Runner.new
10
- runner.instance_variable_get('@reviews').map(&:class).should include(RailsBestPractices::Plugins::Reviews::NotUseRailsRootReview)
17
+ context "given a path that does not end with a slash" do
18
+ before { RailsBestPractices::Core::Runner.base_path = 'spec/fixtures' }
19
+ it_should_behave_like 'load_plugin_reviews'
11
20
  end
12
21
  end
13
22
  end
@@ -8,6 +8,7 @@ describe Sexp do
8
8
  def test
9
9
  ActiveRecord::Base.connection
10
10
  end
11
+ alias :test_new :test
11
12
  end
12
13
  EOF
13
14
  @node = parse_content(content)
@@ -28,6 +29,10 @@ describe Sexp do
28
29
  it "should return const path line" do
29
30
  @node.grep_node(:sexp_type => :const_path_ref).line.should == 3
30
31
  end
32
+
33
+ it "should return alias line" do
34
+ @node.grep_node(:sexp_type => :alias).line.should == 5
35
+ end
31
36
  end
32
37
 
33
38
  describe "grep_nodes" do
@@ -239,6 +244,11 @@ describe Sexp do
239
244
  node = parse_content("puts 'hello', 'world'").grep_node(:sexp_type => :args_add_block)
240
245
  node.all.map(&:to_s).should == ["hello", "world"]
241
246
  end
247
+
248
+ it "no error for args_add_star" do
249
+ node = parse_content("send(:\"\#{route}_url\", *args)").grep_node(:sexp_type => :args_add_block)
250
+ lambda { node.all }.should_not raise_error
251
+ end
242
252
  end
243
253
 
244
254
  describe "conditional_statement" do
@@ -410,6 +420,41 @@ describe Sexp do
410
420
  node = parse_content("[]").grep_node(:sexp_type => :array)
411
421
  node.array_values.should == []
412
422
  end
423
+
424
+ it "should get array value with qwords" do
425
+ node = parse_content("%w(first_name last_name)").grep_node(:sexp_type => :qwords_add)
426
+ node.array_values.map(&:to_s).should == ["first_name", "last_name"]
427
+ end
428
+ end
429
+
430
+ describe "alias" do
431
+ context "method" do
432
+ before do
433
+ @node = parse_content("alias new old").grep_node(:sexp_type => :alias)
434
+ end
435
+
436
+ it "should get old_method" do
437
+ @node.old_method.to_s.should == "old"
438
+ end
439
+
440
+ it "should get new_method" do
441
+ @node.new_method.to_s.should == "new"
442
+ end
443
+ end
444
+
445
+ context "symbol" do
446
+ before do
447
+ @node = parse_content("alias :new :old").grep_node(:sexp_type => :alias)
448
+ end
449
+
450
+ it "should get old_method" do
451
+ @node.old_method.to_s.should == "old"
452
+ end
453
+
454
+ it "should get new_method" do
455
+ @node.new_method.to_s.should == "new"
456
+ end
457
+ end
413
458
  end
414
459
 
415
460
  describe "to_object" do
@@ -89,6 +89,17 @@ describe RailsBestPractices::Prepares::ControllerPrepare do
89
89
  methods.get_methods("PostsController").map(&:method_name).should == ["index", "show"]
90
90
  end
91
91
 
92
+ it "extend inherited_resources with all actions" do
93
+ content =<<-EOF
94
+ class PostsController < InheritedResources::Base
95
+ actions :all, except: [:show]
96
+ end
97
+ EOF
98
+ runner.prepare('app/controllers/posts_controller.rb', content)
99
+ methods = RailsBestPractices::Prepares.controller_methods
100
+ methods.get_methods("PostsController").map(&:method_name).should == ["index", "new", "create", "edit", "update", "destroy"]
101
+ end
102
+
92
103
  it "DSL inherit_resources" do
93
104
  content =<<-EOF
94
105
  class PostsController
@@ -181,6 +181,41 @@ describe RailsBestPractices::Prepares::ModelPrepare do
181
181
  end
182
182
  end
183
183
 
184
+ context "alias" do
185
+ it "should treat alias as method" do
186
+ content =<<-EOF
187
+ class Post < ActiveRecord::Base
188
+ alias :new :old
189
+ end
190
+ EOF
191
+ runner.prepare("app/models/post.rb", content)
192
+ methods = RailsBestPractices::Prepares.model_methods
193
+ methods.get_methods("Post").map(&:method_name).should == ["new"]
194
+ end
195
+
196
+ it "should treat alias_method as method" do
197
+ content =<<-EOF
198
+ class Post < ActiveRecord::Base
199
+ alias_method :new, :old
200
+ end
201
+ EOF
202
+ runner.prepare("app/models/post.rb", content)
203
+ methods = RailsBestPractices::Prepares.model_methods
204
+ methods.get_methods("Post").map(&:method_name).should == ["new"]
205
+ end
206
+
207
+ it "should treat alias_method_chain as method" do
208
+ content =<<-EOF
209
+ class Post < ActiveRecord::Base
210
+ alias_method_chain :method, :feature
211
+ end
212
+ EOF
213
+ runner.prepare("app/models/post.rb", content)
214
+ methods = RailsBestPractices::Prepares.model_methods
215
+ methods.get_methods("Post").map(&:method_name).should == ["method_with_feature", "method"]
216
+ end
217
+ end
218
+
184
219
  context "no error" do
185
220
  it "should raised for finder_sql option" do
186
221
  content =<<-EOF
@@ -5,7 +5,7 @@ describe RailsBestPractices::Reviews::MoveCodeIntoModelReview do
5
5
 
6
6
  it "should move code into model" do
7
7
  content =<<-EOF
8
- <% if current_user && (current_user == @post.user || @post.editors.include?(current_user)) %>
8
+ <% if current_user && @post.user && (current_user == @post.user || @post.editors.include?(current_user)) %>
9
9
  <%= link_to 'Edit this post', edit_post_url(@post) %>
10
10
  <% end %>
11
11
  EOF
@@ -16,7 +16,7 @@ describe RailsBestPractices::Reviews::MoveCodeIntoModelReview do
16
16
 
17
17
  it "should move code into model with haml" do
18
18
  content =<<-EOF
19
- - if current_user && (current_user == @post.user || @post.editors.include?(current_user))
19
+ - if current_user && @post.user && (current_user == @post.user || @post.editors.include?(current_user))
20
20
  = link_to 'Edit this post', edit_post_url(@post)
21
21
  EOF
22
22
  runner.review('app/views/posts/show.html.haml', content)
@@ -46,4 +46,13 @@ describe RailsBestPractices::Reviews::MoveCodeIntoModelReview do
46
46
  runner.review('app/views/posts/show.html.erb', content)
47
47
  runner.should have(0).errors
48
48
  end
49
+
50
+ it "should not move code into model for multiple calls on same variable node" do
51
+ content =<<-EOF
52
+ <% if !job.company.blank? && job.company.title? %>
53
+ <% end %>
54
+ EOF
55
+ runner.review('app/views/jobs/show.html.erb', content)
56
+ runner.should have(0).errors
57
+ end
49
58
  end
@@ -259,6 +259,68 @@ describe RailsBestPractices::Reviews::RemoveUnusedMethodsInModelsReview do
259
259
  runner.on_complete
260
260
  runner.should have(0).errors
261
261
  end
262
+
263
+ it "should not remove unused methods for send" do
264
+ content =<<-EOF
265
+ class Post < ActiveRecord::Base
266
+ def find(user_id); end
267
+ end
268
+ EOF
269
+ runner.prepare('app/models/post.rb', content)
270
+ runner.review('app/models/post.rb', content)
271
+ content =<<-EOF
272
+ class PostsController < ApplicationController
273
+ def find
274
+ Post.new.send(:find, current_user.id)
275
+ end
276
+ end
277
+ EOF
278
+ runner.review('app/controllers/posts_controller.rb', content)
279
+ runner.on_complete
280
+ runner.should have(0).errors
281
+ end
282
+
283
+ it "should remove unused methods for send string_embexpre" do
284
+ content =<<-EOF
285
+ class Post < ActiveRecord::Base
286
+ def find_first; end
287
+ end
288
+ EOF
289
+ runner.prepare('app/models/post.rb', content)
290
+ runner.review('app/models/post.rb', content)
291
+ content =<<-EOF
292
+ class PostsController < ApplicationController
293
+ def find
294
+ type = "first"
295
+ Post.new.send("find_\#{type}")
296
+ end
297
+ end
298
+ EOF
299
+ runner.review('app/controllers/posts_controller.rb', content)
300
+ runner.on_complete
301
+ runner.should have(1).errors
302
+ end
303
+
304
+ it "should remove unused methods for send variable" do
305
+ content =<<-EOF
306
+ class Post < ActiveRecord::Base
307
+ def first; end
308
+ end
309
+ EOF
310
+ runner.prepare('app/models/post.rb', content)
311
+ runner.review('app/models/post.rb', content)
312
+ content =<<-EOF
313
+ class PostsController < ApplicationController
314
+ def find
315
+ type = "first"
316
+ Post.new.send(type)
317
+ end
318
+ end
319
+ EOF
320
+ runner.review('app/controllers/posts_controller.rb', content)
321
+ runner.on_complete
322
+ runner.should have(1).errors
323
+ end
262
324
  end
263
325
 
264
326
  context "protected" do
@@ -384,4 +446,108 @@ describe RailsBestPractices::Reviews::RemoveUnusedMethodsInModelsReview do
384
446
  runner.errors[0].to_s.should == "app/models/post.rb:2 - remove unused methods (Post#active)"
385
447
  end
386
448
  end
449
+
450
+ context "alias" do
451
+ it "should not remove unused method with alias" do
452
+ content =<<-EOF
453
+ class Post < ActiveRecord::Base
454
+ def old; end
455
+ alias new old
456
+ end
457
+ EOF
458
+ runner.prepare("app/models/post.rb", content)
459
+ runner.review("app/models/post.rb", content)
460
+ content =<<-EOF
461
+ class PostsController < ApplicationController
462
+ def show
463
+ @post.new
464
+ end
465
+ end
466
+ EOF
467
+ runner.review("app/controllers/posts_controller.rb", content)
468
+ runner.on_complete
469
+ runner.should have(0).errors
470
+ end
471
+
472
+ it "should not remove unused method with symbol alias" do
473
+ content =<<-EOF
474
+ class Post < ActiveRecord::Base
475
+ def old; end
476
+ alias :new :old
477
+ end
478
+ EOF
479
+ runner.prepare("app/models/post.rb", content)
480
+ runner.review("app/models/post.rb", content)
481
+ content =<<-EOF
482
+ class PostsController < ApplicationController
483
+ def show
484
+ @post.new
485
+ end
486
+ end
487
+ EOF
488
+ runner.review("app/controllers/posts_controller.rb", content)
489
+ runner.on_complete
490
+ runner.should have(0).errors
491
+ end
492
+
493
+ it "should not remove unused method with alias_method" do
494
+ content =<<-EOF
495
+ class Post < ActiveRecord::Base
496
+ def old; end
497
+ alias_method :new, :old
498
+ end
499
+ EOF
500
+ runner.prepare("app/models/post.rb", content)
501
+ runner.review("app/models/post.rb", content)
502
+ content =<<-EOF
503
+ class PostsController < ApplicationController
504
+ def show
505
+ @post.new
506
+ end
507
+ end
508
+ EOF
509
+ runner.review("app/controllers/posts_controller.rb", content)
510
+ runner.on_complete
511
+ runner.should have(0).errors
512
+ end
513
+
514
+ it "should not remove unused method with alias_method_chain" do
515
+ content =<<-EOF
516
+ class Post < ActiveRecord::Base
517
+ def method_with_feature; end
518
+ alias_method_chain :method, :feature
519
+ end
520
+ EOF
521
+ runner.prepare("app/models/post.rb", content)
522
+ runner.review("app/models/post.rb", content)
523
+ content =<<-EOF
524
+ class PostsController < ApplicationController
525
+ def show
526
+ @post.method
527
+ end
528
+ end
529
+ EOF
530
+ runner.review("app/controllers/posts_controller.rb", content)
531
+ runner.on_complete
532
+ runner.should have(0).errors
533
+ end
534
+ end
535
+
536
+ context "methods hash" do
537
+ it "should not remove unused method with methods hash" do
538
+ content =<<-EOF
539
+ class Post < ActiveRecord::Base
540
+ def to_xml(options = {})
541
+ super options.merge(:exclude => :visible, :methods => [:is_discussion_conversation])
542
+ end
543
+
544
+ def is_discussion_conversation; end
545
+ end
546
+ EOF
547
+ runner.prepare("app/models/post.rb", content)
548
+ runner.review("app/models/post.rb", content)
549
+ runner.on_complete
550
+ runner.should have(0).errors
551
+ end
552
+ end
387
553
  end
@@ -55,6 +55,26 @@ describe RailsBestPractices::Reviews::RestrictAutoGeneratedRoutesReview do
55
55
  runner.should have(0).errors
56
56
  end
57
57
 
58
+ it "should not restrict auto-generated routes with :only => :none" do
59
+ content =<<-EOF
60
+ ActionController::Routing::Routes.draw do |map|
61
+ map.resources :posts, :only => :none
62
+ end
63
+ EOF
64
+ runner.review('config/routes.rb', content)
65
+ runner.should have(0).errors
66
+ end
67
+
68
+ it "should not restrict auto-generated routes with :except => :all" do
69
+ content =<<-EOF
70
+ ActionController::Routing::Routes.draw do |map|
71
+ map.resources :posts, :except => :all
72
+ end
73
+ EOF
74
+ runner.review('config/routes.rb', content)
75
+ runner.should have(0).errors
76
+ end
77
+
58
78
  describe "specify a controller" do
59
79
  it "should restrict auto-generated routes" do
60
80
  content =<<-EOF
@@ -14,7 +14,7 @@ describe RailsBestPractices::Reviews::SimplifyRenderInViewsReview do
14
14
 
15
15
  it "should simplify render partial with object" do
16
16
  content =<<-EOF
17
- <%= render :partial => 'posts/post', :object => @post %>
17
+ <%= render :partial => 'post', :object => @post %>
18
18
  EOF
19
19
  runner.review('app/views/posts/index.html.erb', content)
20
20
  runner.should have(1).errors
@@ -32,7 +32,7 @@ describe RailsBestPractices::Reviews::SimplifyRenderInViewsReview do
32
32
 
33
33
  it "should simplify render partial with local variables" do
34
34
  content =<<-EOF
35
- <%= render :partial => 'comments/comment', :locals => { :parent => post } %>
35
+ <%= render :partial => 'comment', :locals => { :parent => post } %>
36
36
  EOF
37
37
  runner.review('app/views/posts/index.html.erb', content)
38
38
  runner.should have(1).errors
@@ -66,7 +66,15 @@ describe RailsBestPractices::Reviews::SimplifyRenderInViewsReview do
66
66
 
67
67
  it "should not simplify render partial with local variables" do
68
68
  content =<<-EOF
69
- <%= render 'comments/comment', :parent => post %>
69
+ <%= render 'comment', :parent => post %>
70
+ EOF
71
+ runner.review('app/views/posts/index.html.erb', content)
72
+ runner.should have(0).errors
73
+ end
74
+
75
+ it "should not simplify render partial with complex partial" do
76
+ content =<<-EOF
77
+ <%= render :partial => 'shared/post', :object => @post %>
70
78
  EOF
71
79
  runner.review('app/views/posts/index.html.erb', content)
72
80
  runner.should have(0).errors
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,8 @@ RSpec.configure do |config|
8
8
  config.after do
9
9
  RailsBestPractices::Prepares.clear
10
10
  end
11
+ config.filter_run :focus => true
12
+ config.run_all_when_everything_filtered = true
11
13
  end
12
14
 
13
15
  def parse_content(content)
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: rails_best_practices
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.2.0
5
+ version: 1.3.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Richard Huang
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-10-16 00:00:00 +08:00
13
+ date: 2011-11-13 00:00:00 +08:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -298,7 +298,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
298
298
  requirements:
299
299
  - - ">="
300
300
  - !ruby/object:Gem::Version
301
- hash: 1140723947848304714
301
+ hash: 3527788243321139328
302
302
  segments:
303
303
  - 0
304
304
  version: "0"