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.
- data/README.md +20 -0
- data/assets/result.html.haml +1 -1
- data/lib/rails_best_practices.rb +5 -3
- data/lib/rails_best_practices/core.rb +1 -1
- data/lib/rails_best_practices/core/check.rb +9 -12
- data/lib/rails_best_practices/core/checking_visitor.rb +9 -6
- data/lib/rails_best_practices/core/model_associations.rb +1 -1
- data/lib/rails_best_practices/core/nil.rb +5 -1
- data/lib/rails_best_practices/core/runner.rb +5 -5
- data/lib/rails_best_practices/core_ext/sexp.rb +688 -0
- data/lib/rails_best_practices/prepares/mailer_prepare.rb +4 -5
- data/lib/rails_best_practices/prepares/model_prepare.rb +16 -26
- data/lib/rails_best_practices/prepares/schema_prepare.rb +11 -17
- data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +24 -75
- data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +39 -113
- data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +6 -16
- data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +16 -32
- data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +11 -20
- data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +7 -28
- data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +16 -14
- data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +10 -28
- data/lib/rails_best_practices/reviews/move_code_into_model_review.rb +12 -11
- data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +13 -24
- data/lib/rails_best_practices/reviews/move_model_logic_into_model_review.rb +9 -9
- data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +24 -68
- data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +15 -22
- data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +31 -91
- data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +4 -2
- data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +20 -18
- data/lib/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review.rb +5 -3
- data/lib/rails_best_practices/reviews/review.rb +8 -37
- data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +10 -6
- data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +9 -6
- data/lib/rails_best_practices/reviews/use_before_filter_review.rb +14 -72
- data/lib/rails_best_practices/reviews/use_model_association_review.rb +19 -31
- data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +5 -5
- data/lib/rails_best_practices/reviews/use_observer_review.rb +22 -40
- data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +34 -39
- data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +14 -38
- data/lib/rails_best_practices/reviews/use_scope_access_review.rb +13 -44
- data/lib/rails_best_practices/version.rb +1 -1
- data/spec/rails_best_practices/core/check_spec.rb +5 -5
- data/spec/rails_best_practices/core/checking_visitor_spec.rb +4 -4
- data/spec/rails_best_practices/core/model_associations_spec.rb +4 -4
- data/spec/rails_best_practices/core/nil_spec.rb +7 -1
- data/spec/rails_best_practices/core_ext/sexp_spec.rb +430 -0
- data/spec/rails_best_practices/prepares/model_prepare_spec.rb +12 -12
- data/spec/rails_best_practices/prepares/schema_prepare_spec.rb +6 -6
- data/spec/rails_best_practices/reviews/move_code_into_controller_review_spec.rb +14 -2
- data/spec/rails_best_practices/reviews/move_code_into_helper_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/needless_deep_nesting_review_spec.rb +3 -3
- data/spec/rails_best_practices/reviews/not_use_default_route_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +15 -1
- data/spec/rails_best_practices/reviews/simplify_render_in_controllers_review_spec.rb +3 -3
- data/spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/use_scope_access_review_spec.rb +4 -4
- data/spec/rails_best_practices_spec.rb +1 -3
- data/spec/spec_helper.rb +4 -0
- metadata +6 -8
- data/lib/rails_best_practices/core/visitable_sexp.rb +0 -444
- data/spec/rails_best_practices/core/visitable_sexp_spec.rb +0 -272
- 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
|
|
data/assets/result.html.haml
CHANGED
data/lib/rails_best_practices.rb
CHANGED
@@ -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
|
-
|
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
|
211
|
+
plain_output("\nNo warning found. Cool!", 'green')
|
210
212
|
else
|
211
|
-
plain_output("\nFound #{@runner.errors.size}
|
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
|
-
|
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
|
32
|
+
# delegate to start_### according to the sexp_type, like
|
36
33
|
#
|
37
34
|
# start_call
|
38
|
-
#
|
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.
|
40
|
+
self.send("start_#{node.sexp_type}", node)
|
44
41
|
end
|
45
42
|
|
46
|
-
# delegate to end_### according to the
|
43
|
+
# delegate to end_### according to the sexp_type, like
|
47
44
|
#
|
48
45
|
# end_call
|
49
|
-
#
|
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.
|
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
|
-
#
|
75
|
-
#
|
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
|
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
|
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
|
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
|
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.
|
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|
|
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] = {
|
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)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'rubygems'
|
3
|
-
require '
|
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(
|
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(
|
102
|
+
def parse_ruby(content)
|
103
103
|
begin
|
104
|
-
|
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
|