rails_best_practices 0.10.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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