simplabs-excellent 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ = 1.5.0
2
+
3
+ * new check Rails::ValidationsCheck that reports ActiveRecord models that do not validate anything
4
+ * added DuplicationCheck to default checks
5
+ * split DuplicationCheck up into MethodDuplicationCheck and BlockDuplicationCheck
6
+
1
7
  = 1.4.1
2
8
 
3
9
  * added Rake Task (see Rakefile for example)
data/README.rdoc CHANGED
@@ -22,6 +22,15 @@ get a formatted HTML report, just specify <tt>html:<filename></tt>:
22
22
 
23
23
  excellent html:out.html app/models
24
24
 
25
+ You can also use Excellent in a Rake task:
26
+
27
+ require 'simplabs/excellent/rake'
28
+
29
+ Simplabs::Excellent::Rake::ExcellentTask.new(:excellent) do |t|
30
+ t.html = 'doc/excellent.html' # optional, if you don't specify html, output will be written to $stdout
31
+ t.paths = %w(app lib)
32
+ end
33
+
25
34
  == Static analysis
26
35
 
27
36
  A few words regarding static code analysis: Static code analysis tools like Excellent can never really understand the code. They just search for patterns that *might* inidicate problematic code. The word *might* really has to be stressed here since static analysis will usually return a reasonable number of false positives. For example, there might be pretty good reasons for empty +rescue+ blocks that suppress all errors (Excellent itself does it). So, don't try and code with the aim of passing Excellent with zero warnings. That will most likely make your code a mess. Instead use Excellent as a helper to find *possibly* problematic code early.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 1
3
- :minor: 4
4
- :patch: 2
3
+ :minor: 5
4
+ :patch: 0
@@ -0,0 +1,49 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ # This check reports duplicated code. It currently finds repeated identical method calls such as:
10
+ #
11
+ # def method
12
+ # other_method + other_method
13
+ # end
14
+ #
15
+ # ==== Applies to
16
+ #
17
+ # * blocks
18
+ class BlockDuplicationCheck < Base
19
+
20
+ DEFAULT_THRESHOLD = 1
21
+
22
+ def initialize(options = {}) #:nodoc:
23
+ super()
24
+ @threshold = options[:threshold] || DEFAULT_THRESHOLD
25
+ @interesting_nodes = [:iter]
26
+ end
27
+
28
+ def evaluate(context) #:nodoc:
29
+ context.calls.each do |call, number|
30
+ if number > @threshold && call.method != 'new'
31
+ add_warning(
32
+ context,
33
+ '{{block}} calls {{statement}} {{duplication_number}} times.', {
34
+ :block => context.full_name,
35
+ :statement => call.full_name,
36
+ :duplication_number => number
37
+ }
38
+ )
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -15,15 +15,14 @@ module Simplabs
15
15
  # ==== Applies to
16
16
  #
17
17
  # * methods
18
- # * blocks
19
- class DuplicationCheck < Base
18
+ class MethodDuplicationCheck < Base
20
19
 
21
20
  DEFAULT_THRESHOLD = 1
22
21
 
23
22
  def initialize(options = {}) #:nodoc:
24
23
  super()
25
24
  @threshold = options[:threshold] || DEFAULT_THRESHOLD
26
- @interesting_nodes = [:defn, :defs, :iter]
25
+ @interesting_nodes = [:defn, :defs]
27
26
  end
28
27
 
29
28
  def evaluate(context) #:nodoc:
@@ -24,7 +24,7 @@ module Simplabs
24
24
  end
25
25
 
26
26
  def evaluate(context) #:nodoc:
27
- add_warning(context, '{{class}} does not specify attr_accessible.', { :class => context.full_name }) if context.activerecord_model? && !context.specifies_attr_accessible?
27
+ add_warning(context, '{{class}} does not specify attr_accessible.', { :class => context.full_name }) if context.active_record_model? && !context.specifies_attr_accessible?
28
28
  end
29
29
 
30
30
  end
@@ -25,7 +25,7 @@ module Simplabs
25
25
  end
26
26
 
27
27
  def evaluate(context) #:nodoc:
28
- add_warning(context, '{{class}} specifies attr_protected.', { :class => context.full_name }) if context.activerecord_model? && context.specifies_attr_protected?
28
+ add_warning(context, '{{class}} specifies attr_protected.', { :class => context.full_name }) if context.active_record_model? && context.specifies_attr_protected?
29
29
  end
30
30
 
31
31
  end
@@ -0,0 +1,36 @@
1
+ require 'simplabs/excellent/checks/base'
2
+
3
+ module Simplabs
4
+
5
+ module Excellent
6
+
7
+ module Checks
8
+
9
+ module Rails
10
+
11
+ # This check reports +ActiveRecord+ models that do not validate anything. Not validating anything in the model is usually not intended and has
12
+ # most likely just been forgotten. For more information on validations and why to use them, see http://guides.rubyonrails.org/activerecord_validations_callbacks.html#why-use-validations.
13
+ #
14
+ # ==== Applies to
15
+ #
16
+ # * +ActiveRecord+ models
17
+ class ValidationsCheck < Base
18
+
19
+ def initialize #:nodoc:
20
+ super
21
+ @interesting_nodes = [:class]
22
+ end
23
+
24
+ def evaluate(context) #:nodoc:
25
+ add_warning(context, '{{class}} does not validate any attributes.', { :class => context.full_name }) if context.active_record_model? && !context.validating?
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -16,3 +16,4 @@ end
16
16
  require 'simplabs/excellent/checks/rails/attr_accessible_check'
17
17
  require 'simplabs/excellent/checks/rails/attr_protected_check'
18
18
  require 'simplabs/excellent/checks/rails/instance_var_in_partial_check'
19
+ require 'simplabs/excellent/checks/rails/validations_check'
@@ -25,7 +25,8 @@ require 'simplabs/excellent/checks/method_name_check'
25
25
  require 'simplabs/excellent/checks/module_line_count_check'
26
26
  require 'simplabs/excellent/checks/module_name_check'
27
27
  require 'simplabs/excellent/checks/parameter_number_check'
28
- require 'simplabs/excellent/checks/duplication_check'
28
+ require 'simplabs/excellent/checks/method_duplication_check'
29
+ require 'simplabs/excellent/checks/block_duplication_check'
29
30
  require 'simplabs/excellent/checks/nested_iterators_check'
30
31
  require 'simplabs/excellent/checks/flog_method_check'
31
32
  require 'simplabs/excellent/checks/flog_block_check'
@@ -16,6 +16,8 @@ module Simplabs
16
16
  @receiver = exp[1].is_a?(Sexp) ? (exp[1][1].nil? ? exp[1][2].to_s : exp[1][1].to_s) : nil
17
17
  @method = exp[2].to_s
18
18
  @full_name = [@receiver, @method].compact.join('.')
19
+ record_validation
20
+ record_call
19
21
  end
20
22
 
21
23
  def <=>(other)
@@ -27,6 +29,20 @@ module Simplabs
27
29
  @full_name.hash
28
30
  end
29
31
 
32
+ private
33
+
34
+ def record_call
35
+ parent = @parent
36
+ parent = parent.parent until parent.nil? || parent.is_a?(MethodContext) || parent.is_a?(BlockContext) || parent.is_a?(SingletonMethodContext)
37
+ parent.record_call_to(self) if parent
38
+ end
39
+
40
+ def record_validation
41
+ if ClassContext::VALIDATIONS.include?(@method) && @parent && @parent.is_a?(ClassContext) && @parent.active_record_model?
42
+ @parent.validations << @method
43
+ end
44
+ end
45
+
30
46
  end
31
47
 
32
48
  end
@@ -11,21 +11,38 @@ module Simplabs
11
11
  include FlogMeasure
12
12
  include Scopeable
13
13
 
14
+ VALIDATIONS = %w(
15
+ validates_acceptance_of
16
+ validates_associated
17
+ validates_confirmation_of
18
+ validates_each
19
+ validates_exclusion_of
20
+ validates_format_of
21
+ validates_inclusion_of
22
+ validates_length_of
23
+ validates_numericality_of
24
+ validates_presence_of
25
+ validates_size_of
26
+ validates_uniqueness_of
27
+ )
28
+
14
29
  attr_reader :methods
15
30
  attr_reader :line_count
16
31
  attr_reader :base_class_name
32
+ attr_reader :validations
17
33
 
18
34
  def initialize(exp, parent)
19
35
  super
20
36
  @name, @full_name = get_names
21
- @base_class_name = get_base_class_name
22
- @methods = []
23
- @line_count = count_lines
24
- @attr_accessible = false
25
- @attr_protected = false
37
+ @base_class_name = get_base_class_name
38
+ @methods = []
39
+ @line_count = count_lines
40
+ @attr_accessible = false
41
+ @attr_protected = false
42
+ @validations = []
26
43
  end
27
44
 
28
- def activerecord_model?
45
+ def active_record_model?
29
46
  @base_class_name == 'ActiveRecord::Base'
30
47
  end
31
48
 
@@ -37,6 +54,10 @@ module Simplabs
37
54
  @attr_protected
38
55
  end
39
56
 
57
+ def validating?
58
+ !@validations.empty? || @methods.any?{ |method| %(validate validate_on_create validate_on_update).include?(method.name) }
59
+ end
60
+
40
61
  def process_call(exp)
41
62
  @attr_accessible = true if exp[2] == :attr_accessible
42
63
  @attr_protected = true if exp[2] == :attr_protected
@@ -97,10 +97,7 @@ module Simplabs
97
97
  end
98
98
 
99
99
  def process_call(exp)
100
- if @contexts.last.is_a?(MethodContext) || @contexts.last.is_a?(BlockContext) || @contexts.last.is_a?(SingletonMethodContext)
101
- @contexts.last.record_call_to(CallContext.new(exp, @contexts.last))
102
- end
103
- process_default(exp)
100
+ process_default(exp, CallContext.new(exp, @contexts.last))
104
101
  end
105
102
 
106
103
  def process_resbody(exp)
@@ -14,8 +14,8 @@ module Simplabs
14
14
  def full_name
15
15
  return @name if @parent.blank?
16
16
  full_name = @name
17
- parent = @parent
18
- parent = parent.parent until parent.is_a?(ClassContext) || parent.is_a?(ModuleContext)
17
+ parent = @parent
18
+ parent = parent.parent until parent.is_a?(ClassContext) || parent.is_a?(ModuleContext)
19
19
  full_name = "#{parent.full_name}.#{full_name}"
20
20
  end
21
21
 
@@ -11,7 +11,7 @@ module Simplabs
11
11
  def initialize(exp, parent)
12
12
  super
13
13
  @contains_assignment = has_assignment?
14
- @tests_parameter = contains_parameter?
14
+ @tests_parameter = contains_parameter?
15
15
  end
16
16
 
17
17
  def tests_assignment?
@@ -37,8 +37,8 @@ module Simplabs
37
37
  "#{parent.full_name}##{@name}"
38
38
  end
39
39
 
40
- def record_call_to(exp)
41
- @calls[exp] += 1
40
+ def record_call_to(context)
41
+ @calls[context] += 1
42
42
  end
43
43
 
44
44
  end
@@ -16,8 +16,8 @@ module Simplabs
16
16
  def initialize(exp, parent)
17
17
  super
18
18
  @name, @full_name = get_names
19
- @methods = []
20
- @line_count = count_lines
19
+ @methods = []
20
+ @line_count = count_lines
21
21
  end
22
22
 
23
23
  end
@@ -1,4 +1,5 @@
1
1
  require 'rake'
2
+ require 'simplabs/excellent'
2
3
 
3
4
  module Simplabs
4
5
 
@@ -29,6 +29,8 @@ module Simplabs
29
29
  :FlogMethodCheck => { },
30
30
  :FlogBlockCheck => { },
31
31
  :FlogClassCheck => { },
32
+ :BlockDuplicationCheck => { },
33
+ :MethodDuplicationCheck => { },
32
34
  :'Rails::AttrProtectedCheck' => { },
33
35
  :'Rails::AttrAccessibleCheck' => { },
34
36
  :'Rails::InstanceVarInPartialCheck' => { }
@@ -9,7 +9,7 @@ module Simplabs #:nodoc:
9
9
 
10
10
  module Excellent #:nodoc:
11
11
 
12
- VERSION = '1.4.2'
12
+ VERSION = '1.5.0'
13
13
 
14
14
  end
15
15
 
@@ -0,0 +1,95 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Simplabs::Excellent::Checks::BlockDuplicationCheck do
4
+
5
+ before do
6
+ @excellent = Simplabs::Excellent::Runner.new(Simplabs::Excellent::Checks::BlockDuplicationCheck.new({ :threshold => 1 }))
7
+ end
8
+
9
+ describe '#evaluate' do
10
+
11
+ it 'should accept multiple calls to new' do
12
+ code = <<-END
13
+ double_thing do
14
+ @thing.new + @thing.new
15
+ end
16
+ END
17
+ @excellent.check_code(code)
18
+ warnings = @excellent.warnings
19
+
20
+ warnings.should be_empty
21
+ end
22
+
23
+ it 'should reject multiple calls to the same method and receiver' do
24
+ code = <<-END
25
+ double_thing do
26
+ @other.thing + @other.thing
27
+ end
28
+ END
29
+
30
+ verify_warning_found(code, '@other.thing')
31
+ end
32
+
33
+ it 'should reject multiple calls to the same lvar' do
34
+ code = <<-END
35
+ double_thing do
36
+ thing[1] + thing[2]
37
+ end
38
+ END
39
+
40
+ verify_warning_found(code, 'thing.[]')
41
+ end
42
+
43
+ it 'should reject multiple calls to the same singleton method' do
44
+ code = <<-END
45
+ double_thing do
46
+ Class.thing[1] + Class.thing[2]
47
+ end
48
+ END
49
+
50
+ verify_warning_found(code, 'Class.thing')
51
+ end
52
+
53
+ it 'should reject multiple calls to the same method without a receiver' do
54
+ code = <<-END
55
+ double_thing do
56
+ thing + thing
57
+ end
58
+ END
59
+
60
+ verify_warning_found(code, 'thing')
61
+ end
62
+
63
+ it 'should reject multiple calls to the same method with the same parameters' do
64
+ code = <<-END
65
+ double_thing do
66
+ thing(1) + thing(1)
67
+ end
68
+ END
69
+
70
+ verify_warning_found(code, 'thing')
71
+ end
72
+
73
+ it 'should reject multiple calls to the same method with different parameters' do
74
+ code = <<-END
75
+ double_thing do
76
+ thing(1) + thing(2)
77
+ end
78
+ END
79
+
80
+ verify_warning_found(code, 'thing')
81
+ end
82
+
83
+ end
84
+
85
+ def verify_warning_found(code, statement)
86
+ @excellent.check_code(code)
87
+ warnings = @excellent.warnings
88
+
89
+ warnings.should_not be_empty
90
+ warnings[0].info.should == { :block => 'block', :statement => statement, :duplication_number => 2 }
91
+ warnings[0].line_number.should == 1
92
+ warnings[0].message.should == "block calls #{statement} 2 times."
93
+ end
94
+
95
+ end
@@ -1,9 +1,9 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
- describe Simplabs::Excellent::Checks::DuplicationCheck do
3
+ describe Simplabs::Excellent::Checks::MethodDuplicationCheck do
4
4
 
5
5
  before do
6
- @excellent = Simplabs::Excellent::Runner.new(Simplabs::Excellent::Checks::DuplicationCheck.new({ :threshold => 1 }))
6
+ @excellent = Simplabs::Excellent::Runner.new(Simplabs::Excellent::Checks::MethodDuplicationCheck.new({ :threshold => 1 }))
7
7
  end
8
8
 
9
9
  describe '#evaluate' do
@@ -112,18 +112,6 @@ describe Simplabs::Excellent::Checks::DuplicationCheck do
112
112
  verify_warning_found(code, 'thing', 'Class.double_thing', 2)
113
113
  end
114
114
 
115
- it 'should also work with blocks' do
116
- code = <<-END
117
- def method
118
- double_thing do
119
- thing(1) + thing(2)
120
- end
121
- end
122
- END
123
-
124
- verify_warning_found(code, 'thing', 'block', 2)
125
- end
126
-
127
115
  end
128
116
 
129
117
  def verify_warning_found(code, statement, method = 'double_thing', line = 1)
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe Simplabs::Excellent::Checks::Rails::ValidationsCheck do
4
+
5
+ before do
6
+ @excellent = Simplabs::Excellent::Runner.new(Simplabs::Excellent::Checks::Rails::ValidationsCheck.new)
7
+ end
8
+
9
+ describe '#evaluate' do
10
+
11
+ it 'should ignore classes that are not active record models' do
12
+ code = <<-END
13
+ class Test
14
+ end
15
+ END
16
+ @excellent.check_code(code)
17
+ warnings = @excellent.warnings
18
+
19
+ warnings.should be_empty
20
+ end
21
+
22
+ it 'should reject an active record model that does not validate anything' do
23
+ code = <<-END
24
+ class User < ActiveRecord::Base
25
+ end
26
+ END
27
+ @excellent.check_code(code)
28
+ warnings = @excellent.warnings
29
+
30
+ warnings.should_not be_empty
31
+ warnings[0].info.should == { :class => 'User' }
32
+ warnings[0].line_number.should == 1
33
+ warnings[0].message.should == 'User does not validate any attributes.'
34
+ end
35
+
36
+ it 'should accept an active record model that validates attributes using a macro method' do
37
+ code = <<-END
38
+ class User < ActiveRecord::Base
39
+ validates_presence_of :login
40
+ end
41
+ END
42
+ @excellent.check_code(code)
43
+ warnings = @excellent.warnings
44
+
45
+ warnings.should be_empty
46
+ end
47
+
48
+ %(validate validate_on_create validate_on_update).each do |method|
49
+
50
+ it "should accept an active record model that validates attribute by overriding #{method}" do
51
+ code = <<-END
52
+ class User < ActiveRecord::Base
53
+ def #{method}
54
+ end
55
+ end
56
+ END
57
+ @excellent.check_code(code)
58
+ warnings = @excellent.warnings
59
+
60
+ warnings.should be_empty
61
+ end
62
+
63
+ end
64
+
65
+ it 'should also work with namespaced models' do
66
+ code = <<-END
67
+ class Backend::User < ActiveRecord::Base
68
+ end
69
+ END
70
+ @excellent.check_code(code)
71
+ warnings = @excellent.warnings
72
+
73
+ warnings.should_not be_empty
74
+ warnings[0].info.should == { :class => 'Backend::User' }
75
+ warnings[0].line_number.should == 1
76
+ warnings[0].message.should == 'Backend::User does not validate any attributes.'
77
+ end
78
+
79
+ end
80
+
81
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplabs-excellent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marco Otte-Witte
@@ -48,6 +48,7 @@ files:
48
48
  - lib/simplabs/excellent/checks/abc_metric_method_check.rb
49
49
  - lib/simplabs/excellent/checks/assignment_in_conditional_check.rb
50
50
  - lib/simplabs/excellent/checks/base.rb
51
+ - lib/simplabs/excellent/checks/block_duplication_check.rb
51
52
  - lib/simplabs/excellent/checks/case_missing_else_check.rb
52
53
  - lib/simplabs/excellent/checks/class_line_count_check.rb
53
54
  - lib/simplabs/excellent/checks/class_name_check.rb
@@ -55,7 +56,6 @@ files:
55
56
  - lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb
56
57
  - lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb
57
58
  - lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb
58
- - lib/simplabs/excellent/checks/duplication_check.rb
59
59
  - lib/simplabs/excellent/checks/empty_rescue_body_check.rb
60
60
  - lib/simplabs/excellent/checks/flog_block_check.rb
61
61
  - lib/simplabs/excellent/checks/flog_check.rb
@@ -63,6 +63,7 @@ files:
63
63
  - lib/simplabs/excellent/checks/flog_method_check.rb
64
64
  - lib/simplabs/excellent/checks/for_loop_check.rb
65
65
  - lib/simplabs/excellent/checks/line_count_check.rb
66
+ - lib/simplabs/excellent/checks/method_duplication_check.rb
66
67
  - lib/simplabs/excellent/checks/method_line_count_check.rb
67
68
  - lib/simplabs/excellent/checks/method_name_check.rb
68
69
  - lib/simplabs/excellent/checks/module_line_count_check.rb
@@ -73,6 +74,7 @@ files:
73
74
  - lib/simplabs/excellent/checks/rails/attr_accessible_check.rb
74
75
  - lib/simplabs/excellent/checks/rails/attr_protected_check.rb
75
76
  - lib/simplabs/excellent/checks/rails/instance_var_in_partial_check.rb
77
+ - lib/simplabs/excellent/checks/rails/validations_check.rb
76
78
  - lib/simplabs/excellent/checks/rails.rb
77
79
  - lib/simplabs/excellent/checks/singleton_variable_check.rb
78
80
  - lib/simplabs/excellent/checks.rb
@@ -113,18 +115,19 @@ files:
113
115
  - lib/simplabs/excellent.rb
114
116
  - spec/checks/abc_metric_method_check_spec.rb
115
117
  - spec/checks/assignment_in_conditional_check_spec.rb
118
+ - spec/checks/block_duplication_check_spec.rb
116
119
  - spec/checks/case_missing_else_check_spec.rb
117
120
  - spec/checks/class_line_count_check_spec.rb
118
121
  - spec/checks/class_name_check_spec.rb
119
122
  - spec/checks/control_coupling_check_spec.rb
120
123
  - spec/checks/cyclomatic_complexity_block_check_spec.rb
121
124
  - spec/checks/cyclomatic_complexity_method_check_spec.rb
122
- - spec/checks/duplication_check_spec.rb
123
125
  - spec/checks/empty_rescue_body_check_spec.rb
124
126
  - spec/checks/flog_block_check_spec.rb
125
127
  - spec/checks/flog_class_check_spec.rb
126
128
  - spec/checks/flog_method_check_spec.rb
127
129
  - spec/checks/for_loop_check_spec.rb
130
+ - spec/checks/method_duplication_check_spec.rb
128
131
  - spec/checks/method_line_count_check_spec.rb
129
132
  - spec/checks/method_name_check_spec.rb
130
133
  - spec/checks/module_line_count_check_spec.rb
@@ -134,6 +137,7 @@ files:
134
137
  - spec/checks/rails/attr_accessible_check_spec.rb
135
138
  - spec/checks/rails/attr_protected_check_spec.rb
136
139
  - spec/checks/rails/instance_var_in_partial_check_spec.rb
140
+ - spec/checks/rails/validations_check_spec.rb
137
141
  - spec/checks/singleton_variable_check_spec.rb
138
142
  - spec/extensions/string_spec.rb
139
143
  - spec/spec_helper.rb