reek 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/README.txt +1 -1
- data/bin/reek +0 -0
- data/lib/reek/method_checker.rb +12 -7
- data/lib/reek/printer.rb +10 -1
- data/lib/reek/report.rb +10 -4
- data/lib/reek/smells.rb +17 -30
- data/lib/reek/version.rb +1 -1
- data/spec/reek/feature_envy_spec.rb +3 -3
- data/spec/reek/long_method_spec.rb +25 -2
- data/spec/reek/nested_iterators_spec.rb +22 -0
- data/spec/reek/report_spec.rb +24 -1
- data/spec/reek/smell_spec.rb +18 -2
- data/website/index.html +1 -1
- metadata +1 -1
data/History.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== 0.1.1 2008-09-09
|
2
|
+
|
3
|
+
* Some tweaks:
|
4
|
+
* Fixed report printing for Feature Envy when the receiver is a block
|
5
|
+
* Fixed: successive iterators reported as nested
|
6
|
+
* Fixed: Long Method now reports the total length of the method
|
7
|
+
* Fixed: each smell reported only once
|
8
|
+
|
1
9
|
== 0.1.0 2008-09-09
|
2
10
|
|
3
11
|
* 1 minor enhancement:
|
data/README.txt
CHANGED
data/bin/reek
CHANGED
File without changes
|
data/lib/reek/method_checker.rb
CHANGED
@@ -12,19 +12,20 @@ module Reek
|
|
12
12
|
super(smells)
|
13
13
|
@class_name = klass_name
|
14
14
|
@description = klass_name
|
15
|
-
@top_level_block = true
|
16
15
|
@calls = Hash.new(0)
|
17
16
|
@lvars = Set.new
|
18
17
|
@inside_an_iter = false
|
19
18
|
end
|
20
19
|
|
21
20
|
def process_defn(exp)
|
21
|
+
@num_statements = 0
|
22
22
|
@description = "#{@class_name}##{exp[1]}"
|
23
23
|
UncommunicativeName.check(exp[1], self, 'method')
|
24
24
|
process(exp[2])
|
25
25
|
@lvars.each {|lvar| UncommunicativeName.check(lvar, self, 'local variable') }
|
26
26
|
UtilityFunction.check(@calls, self)
|
27
27
|
FeatureEnvy.check(@calls, self)
|
28
|
+
LongMethod.check(@num_statements, self)
|
28
29
|
s(exp)
|
29
30
|
end
|
30
31
|
|
@@ -41,18 +42,14 @@ module Reek
|
|
41
42
|
|
42
43
|
def process_iter(exp)
|
43
44
|
NestedIterators.check(@inside_an_iter, self)
|
44
|
-
@top_level_block = false
|
45
45
|
@inside_an_iter = true
|
46
46
|
exp[1..-1].each { |s| process(s) }
|
47
|
+
@inside_an_iter = false
|
47
48
|
s(exp)
|
48
49
|
end
|
49
50
|
|
50
51
|
def process_block(exp)
|
51
|
-
|
52
|
-
LongMethod.check(exp, self)
|
53
|
-
else
|
54
|
-
LongBlock.check(exp, self)
|
55
|
-
end
|
52
|
+
@num_statements += count_statements(exp)
|
56
53
|
exp[1..-1].each { |s| process(s) }
|
57
54
|
s(exp)
|
58
55
|
end
|
@@ -108,5 +105,13 @@ module Reek
|
|
108
105
|
process(exp[2])
|
109
106
|
s(exp)
|
110
107
|
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def count_statements(exp)
|
112
|
+
result = exp.length - 1
|
113
|
+
result -= 1 if Array === exp[1] and exp[1][0] == :args
|
114
|
+
result
|
115
|
+
end
|
111
116
|
end
|
112
117
|
end
|
data/lib/reek/printer.rb
CHANGED
@@ -19,7 +19,11 @@ module Reek
|
|
19
19
|
|
20
20
|
def print(sexp)
|
21
21
|
@report = sexp.inspect
|
22
|
-
|
22
|
+
begin
|
23
|
+
process(sexp)
|
24
|
+
rescue
|
25
|
+
raise "Error in print, parsing:\n #{sexp.inspect}"
|
26
|
+
end
|
23
27
|
@report
|
24
28
|
end
|
25
29
|
|
@@ -43,6 +47,11 @@ module Reek
|
|
43
47
|
s(exp)
|
44
48
|
end
|
45
49
|
|
50
|
+
def process_iter(exp)
|
51
|
+
@report = 'block'
|
52
|
+
s(exp)
|
53
|
+
end
|
54
|
+
|
46
55
|
def process_call(exp)
|
47
56
|
@report = "#{exp[1]}.#{exp[2]}"
|
48
57
|
@report += "(#{exp[3]})" if exp.length > 3
|
data/lib/reek/report.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
|
+
require 'set'
|
4
|
+
|
3
5
|
module Reek
|
4
6
|
|
5
7
|
class Report
|
6
8
|
def initialize # :nodoc:
|
7
|
-
@smells =
|
9
|
+
@smells = SortedSet.new
|
8
10
|
end
|
9
11
|
|
10
12
|
def <<(smell) # :nodoc:
|
@@ -14,13 +16,17 @@ module Reek
|
|
14
16
|
def empty? # :nodoc:
|
15
17
|
@smells.empty?
|
16
18
|
end
|
17
|
-
|
19
|
+
|
20
|
+
def include?(obj) # :nodoc:
|
21
|
+
@smells.include?(obj)
|
22
|
+
end
|
23
|
+
|
18
24
|
def length # :nodoc:
|
19
25
|
@smells.length
|
20
26
|
end
|
21
|
-
|
27
|
+
|
22
28
|
def [](i) # :nodoc:
|
23
|
-
@smells[i]
|
29
|
+
@smells.to_a[i]
|
24
30
|
end
|
25
31
|
|
26
32
|
# Creates a formatted report of all the smells recorded in
|
data/lib/reek/smells.rb
CHANGED
@@ -5,6 +5,8 @@ require 'reek/printer'
|
|
5
5
|
module Reek
|
6
6
|
|
7
7
|
class Smell
|
8
|
+
include Comparable
|
9
|
+
|
8
10
|
def self.convert_camel_case(class_name)
|
9
11
|
class_name.gsub(/([a-z])([A-Z])/) { |s| "#{$1} #{$2}"}
|
10
12
|
end
|
@@ -18,8 +20,16 @@ module Reek
|
|
18
20
|
context.report(smell) if smell.recognise?(exp)
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
22
|
-
|
23
|
+
def hash # :nodoc:
|
24
|
+
report.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def <=>(other) # :nodoc:
|
28
|
+
self.report <=> other.report
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(other) # :nodoc:
|
32
|
+
self.report.eql?(other.report)
|
23
33
|
end
|
24
34
|
|
25
35
|
def name
|
@@ -62,36 +72,13 @@ module Reek
|
|
62
72
|
class LongMethod < Smell
|
63
73
|
MAX_ALLOWED = 5
|
64
74
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
result
|
69
|
-
end
|
70
|
-
|
71
|
-
def recognise?(exp)
|
72
|
-
count_statements(exp) > MAX_ALLOWED
|
73
|
-
end
|
74
|
-
|
75
|
-
def detailed_report
|
76
|
-
"#{@context} has > #{MAX_ALLOWED} statements"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class LongBlock < Smell
|
81
|
-
MAX_ALLOWED = 5
|
82
|
-
|
83
|
-
def count_statements(exp)
|
84
|
-
result = exp.length - 1
|
85
|
-
result -= 1 if Array === exp[1] and exp[1][0] == :args
|
86
|
-
result
|
87
|
-
end
|
88
|
-
|
89
|
-
def recognise?(exp)
|
90
|
-
count_statements(exp) > MAX_ALLOWED
|
75
|
+
def recognise?(num_stmts)
|
76
|
+
@num_stmts = num_stmts
|
77
|
+
num_stmts > MAX_ALLOWED
|
91
78
|
end
|
92
79
|
|
93
80
|
def detailed_report
|
94
|
-
"#{@context} has
|
81
|
+
"#{@context} has approx #{@num_stmts} statements"
|
95
82
|
end
|
96
83
|
end
|
97
84
|
|
@@ -105,7 +92,7 @@ module Reek
|
|
105
92
|
max = calls.empty? ? 0 : calls.values.max
|
106
93
|
return false unless max > calls[:self]
|
107
94
|
receivers = calls.keys.select { |key| calls[key] == max }
|
108
|
-
@receiver = receivers.map {|r| Printer.print(r)}.sort.join('
|
95
|
+
@receiver = receivers.map {|r| Printer.print(r)}.sort.join(' and ')
|
109
96
|
return true
|
110
97
|
end
|
111
98
|
|
data/lib/reek/version.rb
CHANGED
@@ -25,8 +25,8 @@ describe MethodChecker, "(Feature Envy)" do
|
|
25
25
|
it 'should report simple parameter call' do
|
26
26
|
@cchk.check_source('def simple(arga) arga[3] end')
|
27
27
|
@rpt.length.should == 2
|
28
|
-
@rpt[
|
29
|
-
@rpt[
|
28
|
+
@rpt[1].should == UtilityFunction.new(@cchk)
|
29
|
+
@rpt[0].should == FeatureEnvy.new(@cchk, ':arga')
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'should report highest affinity' do
|
@@ -38,7 +38,7 @@ describe MethodChecker, "(Feature Envy)" do
|
|
38
38
|
it 'should report multiple affinities' do
|
39
39
|
@cchk.check_source('def simple(arga) s1 = ""; s1.to_s; s2 = ""; s2.to_s; @m = 34; end')
|
40
40
|
@rpt.length.should == 1
|
41
|
-
@rpt[0].should == FeatureEnvy.new(@cchk, ':s1
|
41
|
+
@rpt[0].should == FeatureEnvy.new(@cchk, ':s1 and :s2')
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'should not reference global variables' do
|
@@ -20,7 +20,31 @@ describe MethodChecker, "(Long Method)" do
|
|
20
20
|
it 'should report long methods' do
|
21
21
|
@cchk.check_source('def long(arga) alf = f(1);@bet = 2;@cut = 3;@dit = 4; @emp = 5;@fry = 6;end')
|
22
22
|
@rpt.length.should == 1
|
23
|
-
@rpt[0].should
|
23
|
+
@rpt[0].should be_instance_of(LongMethod)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should only report a long method once' do
|
27
|
+
source =<<EOS
|
28
|
+
def standard_entries(rbconfig)
|
29
|
+
@abc = rbconfig
|
30
|
+
rubypath = File.join(@abc['bindir'], @abc['ruby_install_name'] + c['EXEEXT'])
|
31
|
+
major = c['MAJOR'].to_i
|
32
|
+
minor = c['MINOR'].to_i
|
33
|
+
teeny = c['TEENY'].to_i
|
34
|
+
version = ""
|
35
|
+
if c['rubylibdir']
|
36
|
+
libruby = "/lib/ruby"
|
37
|
+
librubyver = "/lib/ruby/"
|
38
|
+
librubyverarch = "/lib/ruby/"
|
39
|
+
siteruby = "lib/ruby/version/site_ruby"
|
40
|
+
siterubyver = siteruby
|
41
|
+
siterubyverarch = "$siterubyver/['arch']}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
EOS
|
45
|
+
@cchk.check_source(source)
|
46
|
+
@rpt.length.should == 1
|
47
|
+
@rpt[0].should be_instance_of(LongMethod)
|
24
48
|
end
|
25
49
|
end
|
26
50
|
|
@@ -47,6 +71,5 @@ end
|
|
47
71
|
EOS
|
48
72
|
@cchk.check_source(src)
|
49
73
|
@rpt.length.should == 1
|
50
|
-
@rpt[0].report.should match(/block/)
|
51
74
|
end
|
52
75
|
end
|
@@ -16,5 +16,27 @@ describe MethodChecker, " nested iterators" do
|
|
16
16
|
@chk.check_source('def bad(fred) @fred.each {|item| item.each {|ting| ting.ting} } end')
|
17
17
|
@rpt.length.should == 1
|
18
18
|
end
|
19
|
+
|
20
|
+
it "should not report method with successive iterators" do
|
21
|
+
source =<<EOS
|
22
|
+
def bad(fred)
|
23
|
+
@fred.each {|item| item.each }
|
24
|
+
@jim.each {|item| item.each }
|
25
|
+
end
|
26
|
+
EOS
|
27
|
+
@chk.check_source(source)
|
28
|
+
@rpt.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should report nested iterators only once per method" do
|
32
|
+
source =<<EOS
|
33
|
+
def bad(fred)
|
34
|
+
@fred.each {|item| item.each {|part| @jim.send} }
|
35
|
+
@jim.each {|item| item.each {|piece| @fred.send} }
|
36
|
+
end
|
37
|
+
EOS
|
38
|
+
@chk.check_source(source)
|
39
|
+
@rpt.length.should == 1
|
40
|
+
end
|
19
41
|
end
|
20
42
|
|
data/spec/reek/report_spec.rb
CHANGED
@@ -1,12 +1,26 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
2
|
|
3
3
|
require 'reek/method_checker'
|
4
|
+
require 'reek/smells'
|
4
5
|
require 'reek/report'
|
5
6
|
|
6
7
|
include Reek
|
7
8
|
|
8
|
-
describe Report, "
|
9
|
+
describe Report, " when empty" do
|
10
|
+
before(:each) do
|
11
|
+
@rpt = Report.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should have zero length' do
|
15
|
+
@rpt.length.should == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should claim to be empty' do
|
19
|
+
@rpt.should be_empty
|
20
|
+
end
|
21
|
+
end
|
9
22
|
|
23
|
+
describe Report, "to_s" do
|
10
24
|
before(:each) do
|
11
25
|
rpt = Report.new
|
12
26
|
chk = MethodChecker.new(rpt, 'Thing')
|
@@ -24,3 +38,12 @@ describe Report, "to_s" do
|
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
41
|
+
describe Report, " as a SortedSet" do
|
42
|
+
it 'should only add a smell once' do
|
43
|
+
rpt = Report.new
|
44
|
+
rpt << UtilityFunction.new(self)
|
45
|
+
rpt.length.should == 1
|
46
|
+
rpt << UtilityFunction.new(self)
|
47
|
+
rpt.length.should == 1
|
48
|
+
end
|
49
|
+
end
|
data/spec/reek/smell_spec.rb
CHANGED
@@ -10,8 +10,24 @@ describe Smell, "camel case converter" do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
it "should display correct name in report" do
|
13
|
-
|
14
|
-
smell = FeatureEnvy.new(mchk, [:lvar, :fred])
|
13
|
+
smell = FeatureEnvy.new(self, [:lvar, :fred])
|
15
14
|
smell.report.should match(/[#{smell.name}]/)
|
16
15
|
end
|
17
16
|
end
|
17
|
+
|
18
|
+
describe Smell, ' in comparisons' do
|
19
|
+
it 'should hash equal when the smell is the same' do
|
20
|
+
UtilityFunction.new(self).hash.should == UtilityFunction.new(self).hash
|
21
|
+
NestedIterators.new(self).hash.should == NestedIterators.new(self).hash
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should compare equal when the smell is the same' do
|
25
|
+
UtilityFunction.new(self).should == UtilityFunction.new(self)
|
26
|
+
NestedIterators.new(self).should == NestedIterators.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should compare equal when using <=>' do
|
30
|
+
(UtilityFunction.new(self) <=> UtilityFunction.new(self)).should == 0
|
31
|
+
(NestedIterators.new(self) <=> NestedIterators.new(self)).should == 0
|
32
|
+
end
|
33
|
+
end
|
data/website/index.html
CHANGED
@@ -33,7 +33,7 @@
|
|
33
33
|
<h1>Code smell detector</h1>
|
34
34
|
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/reek"; return false'>
|
35
35
|
<p>Get Version</p>
|
36
|
-
<a href="http://rubyforge.org/projects/reek" class="numbers">0.1.
|
36
|
+
<a href="http://rubyforge.org/projects/reek" class="numbers">0.1.1</a>
|
37
37
|
</div>
|
38
38
|
<h1>→ ‘reek’</h1>
|
39
39
|
|