simplabs-excellent 1.4.2 → 1.5.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/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