rails_best_practices 0.10.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/README.md +20 -0
  2. data/assets/result.html.haml +1 -1
  3. data/lib/rails_best_practices.rb +5 -3
  4. data/lib/rails_best_practices/core.rb +1 -1
  5. data/lib/rails_best_practices/core/check.rb +9 -12
  6. data/lib/rails_best_practices/core/checking_visitor.rb +9 -6
  7. data/lib/rails_best_practices/core/model_associations.rb +1 -1
  8. data/lib/rails_best_practices/core/nil.rb +5 -1
  9. data/lib/rails_best_practices/core/runner.rb +5 -5
  10. data/lib/rails_best_practices/core_ext/sexp.rb +688 -0
  11. data/lib/rails_best_practices/prepares/mailer_prepare.rb +4 -5
  12. data/lib/rails_best_practices/prepares/model_prepare.rb +16 -26
  13. data/lib/rails_best_practices/prepares/schema_prepare.rb +11 -17
  14. data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +24 -75
  15. data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +39 -113
  16. data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +6 -16
  17. data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +16 -32
  18. data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +11 -20
  19. data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +7 -28
  20. data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +16 -14
  21. data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +10 -28
  22. data/lib/rails_best_practices/reviews/move_code_into_model_review.rb +12 -11
  23. data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +13 -24
  24. data/lib/rails_best_practices/reviews/move_model_logic_into_model_review.rb +9 -9
  25. data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +24 -68
  26. data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +15 -22
  27. data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +31 -91
  28. data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +4 -2
  29. data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +20 -18
  30. data/lib/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review.rb +5 -3
  31. data/lib/rails_best_practices/reviews/review.rb +8 -37
  32. data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +10 -6
  33. data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +9 -6
  34. data/lib/rails_best_practices/reviews/use_before_filter_review.rb +14 -72
  35. data/lib/rails_best_practices/reviews/use_model_association_review.rb +19 -31
  36. data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +5 -5
  37. data/lib/rails_best_practices/reviews/use_observer_review.rb +22 -40
  38. data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +34 -39
  39. data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +14 -38
  40. data/lib/rails_best_practices/reviews/use_scope_access_review.rb +13 -44
  41. data/lib/rails_best_practices/version.rb +1 -1
  42. data/spec/rails_best_practices/core/check_spec.rb +5 -5
  43. data/spec/rails_best_practices/core/checking_visitor_spec.rb +4 -4
  44. data/spec/rails_best_practices/core/model_associations_spec.rb +4 -4
  45. data/spec/rails_best_practices/core/nil_spec.rb +7 -1
  46. data/spec/rails_best_practices/core_ext/sexp_spec.rb +430 -0
  47. data/spec/rails_best_practices/prepares/model_prepare_spec.rb +12 -12
  48. data/spec/rails_best_practices/prepares/schema_prepare_spec.rb +6 -6
  49. data/spec/rails_best_practices/reviews/move_code_into_controller_review_spec.rb +14 -2
  50. data/spec/rails_best_practices/reviews/move_code_into_helper_review_spec.rb +1 -1
  51. data/spec/rails_best_practices/reviews/needless_deep_nesting_review_spec.rb +3 -3
  52. data/spec/rails_best_practices/reviews/not_use_default_route_review_spec.rb +1 -1
  53. data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +15 -1
  54. data/spec/rails_best_practices/reviews/simplify_render_in_controllers_review_spec.rb +3 -3
  55. data/spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb +1 -1
  56. data/spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb +1 -1
  57. data/spec/rails_best_practices/reviews/use_scope_access_review_spec.rb +4 -4
  58. data/spec/rails_best_practices_spec.rb +1 -3
  59. data/spec/spec_helper.rb +4 -0
  60. metadata +6 -8
  61. data/lib/rails_best_practices/core/visitable_sexp.rb +0 -444
  62. data/spec/rails_best_practices/core/visitable_sexp_spec.rb +0 -272
  63. data/spec/rails_best_practices/reviews/review_spec.rb +0 -11
data/README.md CHANGED
@@ -58,8 +58,28 @@ Donating
58
58
  Install
59
59
  -------
60
60
 
61
+ **rails_best_practices gem is rewritten based on ripper instead of ruby_parser to support ruby 1.9 new syntax, but not released yet, please check out the source codes and send me errors, thanks.**
62
+
63
+ Ruby 1.9
64
+
65
+ gem install rails_best_practices
66
+
67
+ or add in Gemfile
68
+
69
+ gem "rails_best_practices"
70
+
71
+ if you still want to use rails_best_practices gem in ruby 1.8, please add ripper gem dependency before rails_best_practices.
72
+
73
+ Ruby 1.8
74
+
75
+ gem install ripper
61
76
  gem install rails_best_practices
62
77
 
78
+ or add in Gemfile
79
+
80
+ gem "ripper"
81
+ gem "rails_best_practices"
82
+
63
83
  Issue
64
84
  -----
65
85
 
@@ -48,7 +48,7 @@
48
48
  %tr
49
49
  %th Filename
50
50
  %th Line Number
51
- %th Error Message
51
+ %th Warning Message
52
52
  - errors.each do |error|
53
53
  %tr
54
54
  %td.filename
@@ -126,7 +126,9 @@ module RailsBestPractices
126
126
  @review_files ||= begin
127
127
  files = expand_dirs_to_files(@path)
128
128
  files = file_sort(files)
129
- ['vendor', 'spec', 'test', 'features'].each do |pattern|
129
+
130
+ # By default, tmp, vender, spec, test, features are ignored.
131
+ ['vendor', 'spec', 'test', 'features', 'tmp'].each do |pattern|
130
132
  files = file_ignore(files, "#{pattern}/") unless @options[pattern]
131
133
  end
132
134
 
@@ -206,9 +208,9 @@ module RailsBestPractices
206
208
  @runner.errors.each { |error| plain_output(error.to_s, 'red') }
207
209
  plain_output("\nPlease go to http://rails-bestpractices.com to see more useful Rails Best Practices.", 'green')
208
210
  if @runner.errors.empty?
209
- plain_output("\nNo error found. Cool!", 'green')
211
+ plain_output("\nNo warning found. Cool!", 'green')
210
212
  else
211
- plain_output("\nFound #{@runner.errors.size} errors.", 'red')
213
+ plain_output("\nFound #{@runner.errors.size} warnings.", 'red')
212
214
  end
213
215
  end
214
216
 
@@ -4,10 +4,10 @@ require 'rails_best_practices/core/runner'
4
4
  require 'rails_best_practices/core/checking_visitor'
5
5
  require 'rails_best_practices/core/error'
6
6
  require 'rails_best_practices/core/nil'
7
- require 'rails_best_practices/core/visitable_sexp'
8
7
  require 'rails_best_practices/core/models'
9
8
  require 'rails_best_practices/core/model_associations'
10
9
  require 'rails_best_practices/core/model_attributes'
11
10
  require 'rails_best_practices/core/mailers'
12
11
 
12
+ require 'rails_best_practices/core_ext/sexp'
13
13
  require 'rails_best_practices/core_ext/enumerable'
@@ -3,9 +3,6 @@ module RailsBestPractices
3
3
  module Core
4
4
  # A Check class that takes charge of checking the sexp.
5
5
  class Check
6
- # only nodes whose node_type is in NODE_TYPE will be reviewed.
7
- NODE_TYPES = [:call, :defn, :defs, :if, :class, :module, :lasgn, :iasgn, :ivar, :lvar, :block, :iter, :const]
8
-
9
6
  CONTROLLER_FILES = /controllers\/.*\.rb$/
10
7
  MIGRATION_FILES = /db\/migrate\/.*\.rb$/
11
8
  MODEL_FILES = /models\/.*\.rb$/
@@ -24,7 +21,7 @@ module RailsBestPractices
24
21
 
25
22
  # default interesting nodes.
26
23
  def interesting_nodes
27
- NODE_TYPES
24
+ []
28
25
  end
29
26
 
30
27
  # default interesting files.
@@ -32,26 +29,26 @@ module RailsBestPractices
32
29
  /.*/
33
30
  end
34
31
 
35
- # delegate to start_### according to the node_type, like
32
+ # delegate to start_### according to the sexp_type, like
36
33
  #
37
34
  # start_call
38
- # start_defn
35
+ # start_def
39
36
  #
40
37
  # @param [Sexp] node
41
38
  def node_start(node)
42
39
  @node = node
43
- self.send("start_#{node.node_type}", node)
40
+ self.send("start_#{node.sexp_type}", node)
44
41
  end
45
42
 
46
- # delegate to end_### according to the node_type, like
43
+ # delegate to end_### according to the sexp_type, like
47
44
  #
48
45
  # end_call
49
- # end_defn
46
+ # end_def
50
47
  #
51
48
  # @param [Sexp] node
52
49
  def node_end(node)
53
50
  @node = node
54
- self.send("end_#{node.node_type}", node)
51
+ self.send("end_#{node.sexp_type}", node)
55
52
  end
56
53
 
57
54
  # add error if source code violates rails best practice.
@@ -71,8 +68,8 @@ module RailsBestPractices
71
68
 
72
69
  # method_missing to catch all start and end process for each node type, like
73
70
  #
74
- # start_defn
75
- # end_defn
71
+ # start_def
72
+ # end_def
76
73
  # start_call
77
74
  # end_call
78
75
  #
@@ -7,11 +7,11 @@ module RailsBestPractices
7
7
  # then recursively iterate all sexp nodes,
8
8
  #
9
9
  # for prepare process
10
- # if the node_type and the node filename match the interesting_prepare_nodes and interesting_files,
10
+ # if the sexp_type and the node filename match the interesting_prepare_nodes and interesting_files,
11
11
  # then run the prepare for that node.
12
12
  #
13
13
  # for review process
14
- # if the node_type and the node filename match the interesting_review_nodes and interesting_files,
14
+ # if the sexp_type and the node filename match the interesting_review_nodes and interesting_files,
15
15
  # then run the reivew for that node.
16
16
  class CheckingVisitor
17
17
  # remember all the checks for prepare and review processes according to interesting_nodes.
@@ -41,16 +41,16 @@ module RailsBestPractices
41
41
  end
42
42
 
43
43
  # for prepare process
44
- # if the node_type and the node filename match the interesting_nodes and interesting_files,
44
+ # if the sexp_type and the node filename match the interesting_nodes and interesting_files,
45
45
  # then run the prepare for that node.
46
46
  #
47
47
  # for review process
48
- # if the node_type and the node filename match the interesting_nodes and interesting_files,
48
+ # if the sexp_type and the node filename match the interesting_nodes and interesting_files,
49
49
  # then run the reivew for that node.
50
50
  [:prepare, :review].each do |process|
51
51
  class_eval <<-EOS
52
52
  def #{process}(node) # def review(node)
53
- checks = @#{process}_checks[node.node_type] # checks = @review_checks[node.node_type]
53
+ checks = @#{process}_checks[node.sexp_type] # checks = @review_checks[node.sexp_type]
54
54
  if checks # if checks
55
55
  checks.each { |check| # checks.each { |check|
56
56
  if node.file =~ check.interesting_files # if node.file =~ check.interesting_files
@@ -58,7 +58,10 @@ module RailsBestPractices
58
58
  end # end
59
59
  } # }
60
60
  end # end
61
- node.children.each {|sexp| sexp.#{process}(self)} # node.children.each {|sexp| sexp.review(self)}
61
+ node.children.each { |sexp| # node.children.each { |sexp|
62
+ sexp.file = node.file # sexp.filename = node.file
63
+ sexp.#{process}(self) # sexp.review(self)
64
+ } # }
62
65
  if checks # if checks
63
66
  checks.each { |check| # checks.each { |check|
64
67
  if node.file =~ check.interesting_files # if node.file =~ check.interesting_files
@@ -8,7 +8,7 @@ module RailsBestPractices
8
8
 
9
9
  def add_association(model_name, association_name, association_meta, association_class=nil)
10
10
  @associations[model_name] ||= {}
11
- @associations[model_name][association_name] = {:meta => association_meta, :class_name => association_class || association_name.classify}
11
+ @associations[model_name][association_name] = {"meta" => association_meta, "class_name" => association_class || association_name.classify}
12
12
  end
13
13
 
14
14
  def get_association(model_name, association_name)
@@ -2,7 +2,11 @@
2
2
  module RailsBestPractices
3
3
  module Core
4
4
  class Nil
5
- def to_s(*arguments)
5
+ def hash_size
6
+ 0
7
+ end
8
+
9
+ def to_s
6
10
  self
7
11
  end
8
12
 
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require 'rubygems'
3
- require 'ruby_parser'
3
+ require 'ripper'
4
4
  require 'erubis'
5
5
  require 'yaml'
6
6
  require 'active_support/inflector'
@@ -77,7 +77,8 @@ module RailsBestPractices
77
77
  def #{process}(filename, content) # def review(filename, content)
78
78
  puts filename if @debug # puts filename if @debug
79
79
  content = parse_erb_or_haml(filename, content) # content = parse_erb_or_haml(filename, content)
80
- node = parse_ruby(filename, content) # node = parse_ruby(filename, content)
80
+ node = parse_ruby(content) # node = parse_ruby(content)
81
+ node.file = filename # node.file = filename
81
82
  node.#{process}(@checker) if node # node.review(@checker) if node
82
83
  end # end
83
84
  #
@@ -97,11 +98,10 @@ module RailsBestPractices
97
98
  private
98
99
  # parse ruby code.
99
100
  #
100
- # filename is the filename of the ruby code.
101
101
  # content is the source code of ruby file.
102
- def parse_ruby(filename, content)
102
+ def parse_ruby(content)
103
103
  begin
104
- RubyParser.new.parse(content, filename)
104
+ Sexp.from_array(Ripper::SexpBuilder.new(content).parse)
105
105
  rescue Exception => e
106
106
  if @debug
107
107
  warning = "#{filename} looks like it's not a valid Ruby file. Skipping..."
@@ -0,0 +1,688 @@
1
+ # encoding: utf-8
2
+ require 'sexp'
3
+
4
+ class Sexp
5
+ # prepare current node.
6
+ #
7
+ # @param [RailsBestPractices::Core::CheckingVisitor] visitor the visitor to prepare current node
8
+ def prepare(visitor)
9
+ visitor.prepare(self)
10
+ end
11
+
12
+ # prepare current node.
13
+ #
14
+ # @param [RailsBestPractices::Core::CheckingVisitor] visitor the visitor to review current node
15
+ def review(visitor)
16
+ visitor.review(self)
17
+ end
18
+
19
+ # return the line number of a sexp node.
20
+ #
21
+ # s(:@ident, "test", s(2, 12)
22
+ # => 2
23
+ def line
24
+ if [:def, :command, :command_call, :call, :fcall, :method_add_arg, :method_add_block,
25
+ :var_ref, :const_ref, :class, :module, :if, :unless, :elsif, :binary].include? sexp_type
26
+ self[1].line
27
+ else
28
+ self.last.first if self.last.is_a? Array
29
+ end
30
+ end
31
+
32
+ # return child nodes of a sexp node.
33
+ #
34
+ # @return [Array] child nodes.
35
+ def children
36
+ find_all { | sexp | Sexp === sexp }
37
+ end
38
+
39
+ # recursively find all child nodes, and yeild each child node.
40
+ def recursive_children
41
+ children.each do |child|
42
+ yield child
43
+ child.recursive_children { |c| yield c }
44
+ end
45
+ end
46
+
47
+ # grep all the recursive child nodes with conditions, and yield each match node.
48
+ #
49
+ # @param [Hash] options grep conditions
50
+ #
51
+ # options is the grep conditions, like
52
+ #
53
+ # :sexp_type => :call,
54
+ # :subject => "Post",
55
+ # :message => ["find", "new"]
56
+ #
57
+ # the condition key is one of :sexp_type, :subject or :message,
58
+ # the condition value can be Symbol, Array or Sexp.
59
+ def grep_nodes(options)
60
+ sexp_type = options[:sexp_type]
61
+ subject = options[:subject]
62
+ message = options[:message]
63
+ self.recursive_children do |child|
64
+ if (!sexp_type || (sexp_type.is_a?(Array) ? sexp_type.include?(child.sexp_type) : sexp_type == child.sexp_type)) &&
65
+ (!subject || (subject.is_a?(Array) ? subject.include?(child.subject.to_s) : subject == child.subject.to_s)) &&
66
+ (!message || (message.is_a?(Array) ? message.include?(child.message.to_s) : message == child.message.to_s))
67
+ yield child
68
+ end
69
+ end
70
+ end
71
+
72
+ # grep all the recursive child nodes with conditions, and yield the first match node.
73
+ #
74
+ # @param [Hash] options grep conditions
75
+ #
76
+ # options is the grep conditions, like
77
+ #
78
+ # :sexp_type => :call,
79
+ # :subject => s(:const, Post),
80
+ # :message => [:find, :new]
81
+ #
82
+ # the condition key is one of :sexp_type, :subject, :message,
83
+ # the condition value can be Symbol, Array or Sexp.
84
+ def grep_node(options)
85
+ result = RailsBestPractices::Core::Nil.new
86
+ grep_nodes(options) { |node| result = node; break; }
87
+ result
88
+ end
89
+
90
+ # grep all the recursive child nodes with conditions, and get the count of match nodes.
91
+ #
92
+ # @param [Hash] options grep conditions
93
+ # @return [Integer] the count of metch nodes
94
+ def grep_nodes_count(options)
95
+ count = 0
96
+ grep_nodes(options) { |node| count += 1 }
97
+ count
98
+ end
99
+
100
+ # Get subject node.
101
+ #
102
+ # s(:call,
103
+ # s(:var_ref,
104
+ # s(:@ident, "user", s(1, 0))
105
+ # ),
106
+ # :".",
107
+ # s(:@ident, "name", s(1, 5))
108
+ # )
109
+ # => s(:var_ref,
110
+ # s(:@ident, "user", s(1, 0))
111
+ # )
112
+ #
113
+ # @return [Sexp] subject node
114
+ def subject
115
+ case sexp_type
116
+ when :assign, :field, :call, :binary, :command_call
117
+ self[1]
118
+ when :method_add_arg, :method_add_block
119
+ self[1].subject
120
+ end
121
+ end
122
+
123
+ # Get the class name of the class node.
124
+ #
125
+ # s(:class,
126
+ # s(:const_ref, s(:@const, "User", s(1, 6))),
127
+ # nil,
128
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
129
+ # )
130
+ # => s(:const_ref, s(:@const, "User", s(1, 6))),
131
+ #
132
+ # @return [Sexp] class name node
133
+ def class_name
134
+ if :class == sexp_type
135
+ self[1]
136
+ end
137
+ end
138
+
139
+ # Get the base class of the class node.
140
+ #
141
+ # s(:class,
142
+ # s(:const_ref, s(:@const, "User", s(1, 6))),
143
+ # s(:const_path_ref, s(:var_ref, s(:@const, "ActiveRecord", s(1, 13))), s(:@const, "Base", s(1, 27))),
144
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
145
+ # )
146
+ # => s(:const_path_ref, s(:var_ref, s(:@const, "ActiveRecord", s(1, 13))), s(:@const, "Base", s(1, 27))),
147
+ #
148
+ # @return [Sexp] base class of class node
149
+ def base_class
150
+ if :class == sexp_type
151
+ self[2]
152
+ end
153
+ end
154
+
155
+ # Get the left value of the assign node.
156
+ #
157
+ # s(:assign,
158
+ # s(:var_field, s(:@ident, "user", s(1, 0))),
159
+ # s(:var_ref, s(:@ident, "current_user", s(1, 7)))
160
+ # )
161
+ # => s(:var_field, s(:@ident, "user", s(1, 0))),
162
+ #
163
+ # @return [Symbol] left value of lasgn or iasgn node
164
+ def left_value
165
+ if :assign == sexp_type
166
+ self[1]
167
+ end
168
+ end
169
+
170
+ # Get the right value of assign node.
171
+ #
172
+ # s(:assign,
173
+ # s(:var_field, s(:@ident, "user", s(1, 0))),
174
+ # s(:var_ref, s(:@ident, "current_user", s(1, 7)))
175
+ # )
176
+ # => s(:var_ref, s(:@ident, "current_user", s(1, 7)))
177
+ #
178
+ # @return [Sexp] right value of assign node
179
+ def right_value
180
+ if :assign == sexp_type
181
+ self[2]
182
+ end
183
+ end
184
+
185
+ # Get the message node.
186
+ #
187
+ # s(:command,
188
+ # s(:@ident, "has_many", s(1, 0)),
189
+ # s(:args_add_block,
190
+ # s(:args_add, s(:args_new),
191
+ # s(:symbol_literal, s(:symbol, s(:@ident, "projects", s(1, 10))))
192
+ # ),
193
+ # false
194
+ # )
195
+ # )
196
+ # => s(:@ident, "has_many", s(1, 0)),
197
+ #
198
+ # @return [Symbol] message node
199
+ def message
200
+ case sexp_type
201
+ when :command, :fcall
202
+ self[1]
203
+ when :binary
204
+ self[2]
205
+ when :command_call, :field, :call
206
+ self[3]
207
+ when :method_add_arg, :method_add_block
208
+ self[1].message
209
+ end
210
+ end
211
+
212
+ # Get arguments node.
213
+ #
214
+ # s(:command,
215
+ # s(:@ident, "resources", s(1, 0)),
216
+ # s(:args_add_block,
217
+ # s(:args_add, s(:args_new),
218
+ # s(:symbol_literal, s(:symbol, s(:@ident, "posts", s(1, 11))))
219
+ # ), false
220
+ # )
221
+ # )
222
+ # => s(:args_add_block,
223
+ # s(:args_add, s(:args_new),
224
+ # s(:symbol_literal, s(:symbol, s(:@ident, "posts", s(1, 11))))
225
+ # ), false
226
+ # )
227
+ #
228
+ # @return [Sexp] arguments node
229
+ def arguments
230
+ case sexp_type
231
+ when :command
232
+ self[2]
233
+ when :command_call
234
+ self[4]
235
+ when :method_add_arg
236
+ self[2].arguments
237
+ when :arg_paren
238
+ self[1]
239
+ when :array
240
+ self
241
+ end
242
+ end
243
+
244
+ # Get only argument for binary.
245
+ #
246
+ # s(:binary,
247
+ # s(:var_ref, s(:@ident, "user", s(1, 0))),
248
+ # :==,
249
+ # s(:var_ref, s(:@ident, "current_user", s(1, 8)))
250
+ # )
251
+ # => s(:var_ref, s(:@ident, "current_user", s(1, 8)))
252
+ #
253
+ # @return [Sexp] argument node
254
+ def argument
255
+ if :binary == sexp_type
256
+ self[3]
257
+ end
258
+ end
259
+
260
+ # Get all arguments.
261
+ #
262
+ # s(:args_add_block,
263
+ # s(:args_add,
264
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "hello", s(1, 6))))),
265
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "world", s(1, 15))))
266
+ # ), false
267
+ # )
268
+ # => [
269
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "hello", s(1, 6))))),
270
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "world", s(1, 15))))
271
+ # ]
272
+ #
273
+ # @return [Array] all arguments
274
+ def all
275
+ nodes = []
276
+ case sexp_type
277
+ when :args_add_block, :array
278
+ node = self[1]
279
+ while true
280
+ if :args_add == node.sexp_type
281
+ nodes.unshift node[2]
282
+ node = node[1]
283
+ elsif :args_new == node.sexp_type
284
+ break
285
+ end
286
+ end
287
+ end
288
+ nodes
289
+ end
290
+
291
+ # Get the conditional statement of if node.
292
+ #
293
+ # s(:if,
294
+ # s(:var_ref, s(:@kw, "true", s(1, 3))),
295
+ # s(:stmts_add, s(:stmts_new), s(:void_stmt)),
296
+ # nil
297
+ # )
298
+ # => s(:var_ref, s(:@kw, "true", s(1, 3))),
299
+ #
300
+ # @return [Sexp] conditional statement of if node
301
+ def conditional_statement
302
+ if [:if, :unless, :elsif].include? sexp_type
303
+ self[1]
304
+ end
305
+ end
306
+
307
+ # Get all condition nodes.
308
+ #
309
+ # s(:binary,
310
+ # s(:binary,
311
+ # s(:var_ref, s(:@ident, "user", s(1, 0))),
312
+ # :==,
313
+ # s(:var_ref, s(:@ident, "current_user", s(1, 8)))
314
+ # ),
315
+ # :"&&",
316
+ # s(:call,
317
+ # s(:var_ref, s(:@ident, "user", s(1, 24))),
318
+ # :".",
319
+ # s(:@ident, "valid?", s(1, 29))
320
+ # )
321
+ # )
322
+ # => [
323
+ # s(:binary,
324
+ # s(:var_ref, s(:@ident, "user", s(1, 0))),
325
+ # :==,
326
+ # s(:var_ref, s(:@ident, "current_user", s(1, 8)))
327
+ # ),
328
+ # s(:call,
329
+ # s(:var_ref, s(:@ident, "user", s(1, 24))),
330
+ # :".",
331
+ # s(:@ident, "valid?", s(1, 29))
332
+ # )
333
+ # ]
334
+ #
335
+ # @return [Array] all condition nodes
336
+ def all_conditions
337
+ nodes = []
338
+ if :binary == sexp_type && %w(&& || and or).include?(self[2].to_s)
339
+ if :binary == self[1].sexp_type && %w(&& || and or).include?(self[1][2].to_s)
340
+ nodes += self[1].all_conditions
341
+ else
342
+ nodes << self[1]
343
+ end
344
+ if :binary == self[3].sexp_type && %w(&& || and or).include?(self[3][2].to_s)
345
+ nodes += self[3].all_conditions
346
+ else
347
+ nodes << self[3]
348
+ end
349
+ else
350
+ self
351
+ end
352
+ end
353
+
354
+ # Get the method name of def node.
355
+ #
356
+ # s(:def,
357
+ # s(:@ident, "show", s(1, 4)),
358
+ # s(:params, nil, nil, nil, nil, nil),
359
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
360
+ # )
361
+ # => s(:@ident, "show", s(1, 4)),
362
+ #
363
+ # @return [Sexp] method name node
364
+ def method_name
365
+ if :def == sexp_type
366
+ self[1]
367
+ end
368
+ end
369
+
370
+ # Get body node.
371
+ #
372
+ # s(:class,
373
+ # s(:const_ref, s(:@const, "User", s(1, 6))),
374
+ # nil,
375
+ # s(:bodystmt,
376
+ # s(:stmts_add, s(:stmts_new),
377
+ # s(:def,
378
+ # s(:@ident, "login", s(1, 16)),
379
+ # s(:params, nil, nil, nil, nil, nil),
380
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
381
+ # )
382
+ # ), nil, nil, nil
383
+ # )
384
+ # )
385
+ # => s(:bodystmt,
386
+ # s(:stmts_add, s(:stmts_new),
387
+ # s(:def,
388
+ # s(:@ident, "login", s(1, 16)),
389
+ # s(:params, nil, nil, nil, nil, nil),
390
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
391
+ # )
392
+ # ), nil, nil, nil
393
+ # )
394
+ #
395
+ # @return [Sexp] body node
396
+ def body
397
+ case sexp_type
398
+ when :else
399
+ self[1]
400
+ when :module, :if, :elsif, :unless
401
+ self[2]
402
+ when :class, :def
403
+ self[3]
404
+ when :defs
405
+ self[5]
406
+ end
407
+ end
408
+
409
+ # Get block node.
410
+ #
411
+ # s(:method_add_block,
412
+ # s(:command,
413
+ # s(:@ident, "resources", s(1, 0)),
414
+ # s(:args_add_block, s(:args_add, s(:args_new), s(:symbol_literal, s(:symbol, s(:@ident, "posts", s(1, 11))))), false)
415
+ # ),
416
+ # s(:do_block, nil,
417
+ # s(:stmts_add, s(:stmts_add, s(:stmts_new), s(:void_stmt)),
418
+ # s(:command,
419
+ # s(:@ident, "resources", s(1, 21)),
420
+ # s(:args_add_block, s(:args_add, s(:args_new), s(:symbol_literal, s(:symbol, s(:@ident, "comments", s(1, 32))))), false))
421
+ # )
422
+ # )
423
+ # )
424
+ # => s(:do_block, nil,
425
+ # s(:stmts_add, s(:stmts_add, s(:stmts_new), s(:void_stmt)),
426
+ # s(:command,
427
+ # s(:@ident, "resources", s(1, 21)),
428
+ # s(:args_add_block, s(:args_add, s(:args_new), s(:symbol_literal, s(:symbol, s(:@ident, "comments", s(1, 32))))), false))
429
+ # )
430
+ # )
431
+ #
432
+ # @return [Sexp] body node
433
+ def block
434
+ case sexp_type
435
+ when :method_add_block
436
+ self[2]
437
+ end
438
+ end
439
+
440
+ # Get all statements nodes.
441
+ #
442
+ # s(:bodystmt,
443
+ # s(:stmts_add,
444
+ # s(:stmts_add, s(:stmts_new),
445
+ # s(:def,
446
+ # s(:@ident, "login?", s(1, 16)),
447
+ # s(:params, nil, nil, nil, nil, nil),
448
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
449
+ # )
450
+ # ),
451
+ # s(:def,
452
+ # s(:@ident, "admin?", s(1, 33)),
453
+ # s(:params, nil, nil, nil, nil, nil),
454
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
455
+ # )
456
+ # ), nil, nil, nil
457
+ # )
458
+ # => [
459
+ # s(:def,
460
+ # s(:@ident, "login?", s(1, 16)),
461
+ # s(:params, nil, nil, nil, nil, nil),
462
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
463
+ # ),
464
+ # s(:def,
465
+ # s(:@ident, "admin?", s(1, 33)),
466
+ # s(:params, nil, nil, nil, nil, nil),
467
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
468
+ # )
469
+ # ]
470
+ #
471
+ # @return [Array] all statements
472
+ def statements
473
+ stmts = []
474
+ node = case sexp_type
475
+ when :do_block
476
+ self[2]
477
+ when :bodystmt
478
+ self[1]
479
+ else
480
+ end
481
+ if node
482
+ while true
483
+ if :stmts_add == node.sexp_type && s(:void_stmt) != node[2]
484
+ stmts.unshift node[2]
485
+ node = node[1]
486
+ else
487
+ break
488
+ end
489
+ end
490
+ end
491
+ stmts
492
+ end
493
+
494
+ # Get hash value node.
495
+ #
496
+ # s(:hash,
497
+ # s(:assoclist_from_args,
498
+ # s(
499
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
500
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
501
+ # )
502
+ # )
503
+ # )
504
+ # => s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))
505
+ #
506
+ # @return [Sexp] hash value node
507
+ def hash_value(key)
508
+ pair_nodes = case sexp_type
509
+ when :bare_assoc_hash
510
+ self[1]
511
+ when :hash
512
+ self[1][1]
513
+ else
514
+ end
515
+ if pair_nodes
516
+ pair_nodes.size.times do |i|
517
+ if key == pair_nodes[i][1].to_s
518
+ return pair_nodes[i][2]
519
+ end
520
+ end
521
+ end
522
+ RailsBestPractices::Core::Nil.new
523
+ end
524
+
525
+ # Get hash size.
526
+ #
527
+ # s(:hash,
528
+ # s(:assoclist_from_args,
529
+ # s(
530
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
531
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
532
+ # )
533
+ # )
534
+ # )
535
+ # => 2
536
+ #
537
+ # @return [Integer] hash size
538
+ def hash_size
539
+ case sexp_type
540
+ when :hash
541
+ self[1].hash_size
542
+ when :assoclist_from_args
543
+ self[1].size
544
+ when :bare_assoc_hash
545
+ self[1].size
546
+ end
547
+ end
548
+
549
+ # Get the hash keys.
550
+ #
551
+ # s(:hash,
552
+ # s(:assoclist_from_args,
553
+ # s(
554
+ # s(:assoc_new, s(:@label, "first_name:", s(1, 1)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Richard", s(1, 14))))),
555
+ # s(:assoc_new, s(:@label, "last_name:", s(1, 24)), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "Huang", s(1, 36)))))
556
+ # )
557
+ # )
558
+ # )
559
+ # => ["first_name", "last_name"]
560
+ #
561
+ # @return [Array] hash keys
562
+ def hash_keys
563
+ pair_nodes = case sexp_type
564
+ when :bare_assoc_hash
565
+ self[1]
566
+ when :hash
567
+ self[1][1]
568
+ else
569
+ end
570
+ if pair_nodes
571
+ keys = []
572
+ pair_nodes.size.times do |i|
573
+ keys << pair_nodes[i][1].to_s
574
+ end
575
+ keys
576
+ end
577
+ end
578
+
579
+ # Get the array size.
580
+ #
581
+ # s(:array,
582
+ # s(:args_add,
583
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "first_name", s(1, 2))))),
584
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "last_name", s(1, 16))))
585
+ # )
586
+ # )
587
+ # => 2
588
+ #
589
+ # @return [Integer] array size
590
+ def array_size
591
+ if :array == sexp_type
592
+ first_node = self[1]
593
+ array_size = 0
594
+ while true
595
+ array_size += 1
596
+ first_node = s(:args_new) == first_node[1] ? first_node[2] : first_node[1]
597
+ if :args_add != first_node.sexp_type
598
+ if :array == first_node.sexp_type
599
+ array_size += first_node.array_size
600
+ end
601
+ break
602
+ end
603
+ end
604
+ array_size
605
+ end
606
+ end
607
+
608
+ # To object.
609
+ #
610
+ # s(:array,
611
+ # s(:args_add,
612
+ # s(:args_add, s(:args_new), s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "first_name", s(1, 2))))),
613
+ # s(:string_literal, s(:string_add, s(:string_content), s(:@tstring_content, "last_name", s(1, 16))))
614
+ # )
615
+ # )
616
+ # => ["first_name", "last_name"]
617
+ #
618
+ # @return [Object]
619
+ def to_object
620
+ case sexp_type
621
+ when :array
622
+ arguments.all.map(&:to_s)
623
+ end
624
+ end
625
+
626
+ # to_s.
627
+ #
628
+ # @return [String] to_s
629
+ def to_s
630
+ case sexp_type
631
+ when :string_literal, :xstring_literal, :string_content, :const_ref, :symbol_literal, :symbol,
632
+ :args_add_block, :var_ref, :var_field,
633
+ :@ident, :@tstring_content, :@const, :@ivar, :@kw, :@gvar, :@cvar
634
+ self[1].to_s
635
+ when :string_add
636
+ if s(:string_content) == self[1]
637
+ self[2].to_s
638
+ else
639
+ self[1].to_s
640
+ end
641
+ when :args_add
642
+ if s(:args_new) == self[1]
643
+ self[2].to_s
644
+ else
645
+ self[1].to_s
646
+ end
647
+ when :const_path_ref
648
+ "#{self[1]}::#{self[2]}"
649
+ when :@label
650
+ self[1].to_s[0..-2]
651
+ when :aref
652
+ "#{self[1]}[#{self[2]}]"
653
+ else
654
+ ""
655
+ end
656
+ end
657
+
658
+ def const?
659
+ :@const == self.sexp_type || (:var_ref == self.sexp_type && :@const == self[1].sexp_type)
660
+ end
661
+
662
+ # remove the line and column info from sexp.
663
+ def remove_line_and_column
664
+ node = self.clone
665
+ last_node = node.last
666
+ if Sexp === last_node && last_node.size == 2 && last_node.first.is_a?(Integer) && last_node.last.is_a?(Integer)
667
+ node.delete_at(-1)
668
+ end
669
+ node.sexp_body.each_with_index do |child, index|
670
+ if Sexp === child
671
+ node[index+1] = child.remove_line_and_column
672
+ end
673
+ end
674
+ node
675
+ end
676
+
677
+ # if the return value of these methods is nil, then return RailsBestPractices::Core::Nil.new instead
678
+ [:sexp_type, :subject, :message, :arguments, :argument, :class_name, :base_class, :method_name, :body, :block, :conditional_statement, :left_value, :right_value].each do |method|
679
+ class_eval <<-EOS
680
+ alias_method :origin_#{method}, :#{method}
681
+
682
+ def #{method}
683
+ ret = origin_#{method}
684
+ ret.nil? ? RailsBestPractices::Core::Nil.new : ret
685
+ end
686
+ EOS
687
+ end
688
+ end