reek 0.3.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/History.txt +20 -0
- data/README.txt +4 -80
- data/Rakefile +15 -4
- data/bin/reek +10 -16
- data/config/defaults.reek +53 -0
- data/lib/reek.rb +1 -21
- data/lib/reek/block_context.rb +37 -0
- data/lib/reek/class_context.rb +73 -0
- data/lib/reek/code_context.rb +47 -0
- data/lib/reek/code_parser.rb +204 -0
- data/lib/reek/exceptions.reek +13 -0
- data/lib/reek/if_context.rb +25 -0
- data/lib/reek/method_context.rb +85 -0
- data/lib/reek/module_context.rb +34 -0
- data/lib/reek/name.rb +42 -0
- data/lib/reek/object_refs.rb +3 -6
- data/lib/reek/options.rb +60 -40
- data/lib/reek/rake_task.rb +20 -29
- data/lib/reek/report.rb +16 -27
- data/lib/reek/sexp_formatter.rb +52 -0
- data/lib/reek/singleton_method_context.rb +27 -0
- data/lib/reek/smell_warning.rb +49 -0
- data/lib/reek/smells/control_couple.rb +21 -13
- data/lib/reek/smells/duplication.rb +23 -27
- data/lib/reek/smells/feature_envy.rb +18 -25
- data/lib/reek/smells/large_class.rb +32 -17
- data/lib/reek/smells/long_method.rb +24 -16
- data/lib/reek/smells/long_parameter_list.rb +25 -18
- data/lib/reek/smells/long_yield_list.rb +7 -9
- data/lib/reek/smells/nested_iterators.rb +13 -9
- data/lib/reek/smells/smell_detector.rb +66 -0
- data/lib/reek/smells/smells.rb +71 -10
- data/lib/reek/smells/uncommunicative_name.rb +49 -41
- data/lib/reek/smells/utility_function.rb +18 -18
- data/lib/reek/source.rb +116 -0
- data/lib/reek/spec.rb +146 -0
- data/lib/reek/stop_context.rb +62 -0
- data/lib/reek/yield_call_context.rb +14 -0
- data/reek.gemspec +42 -0
- data/spec/integration/reek_source_spec.rb +20 -0
- data/spec/{script_spec.rb → integration/script_spec.rb} +11 -24
- data/spec/reek/class_context_spec.rb +198 -0
- data/spec/reek/code_context_spec.rb +92 -0
- data/spec/reek/code_parser_spec.rb +44 -0
- data/spec/reek/config_spec.rb +42 -0
- data/spec/reek/if_context_spec.rb +17 -0
- data/spec/reek/method_context_spec.rb +52 -0
- data/spec/reek/module_context_spec.rb +38 -0
- data/spec/reek/options_spec.rb +2 -28
- data/spec/reek/report_spec.rb +6 -40
- data/spec/reek/sexp_formatter_spec.rb +31 -0
- data/spec/reek/singleton_method_context_spec.rb +17 -0
- data/spec/reek/smells/control_couple_spec.rb +10 -18
- data/spec/reek/smells/duplication_spec.rb +53 -32
- data/spec/reek/smells/feature_envy_spec.rb +87 -49
- data/spec/reek/smells/large_class_spec.rb +45 -4
- data/spec/reek/smells/long_method_spec.rb +25 -41
- data/spec/reek/smells/long_parameter_list_spec.rb +30 -76
- data/spec/reek/smells/nested_iterators_spec.rb +19 -29
- data/spec/reek/smells/smell_spec.rb +9 -18
- data/spec/reek/smells/uncommunicative_name_spec.rb +88 -53
- data/spec/reek/smells/utility_function_spec.rb +45 -44
- data/spec/samples/inline_spec.rb +40 -0
- data/spec/samples/optparse_spec.rb +100 -0
- data/spec/samples/redcloth_spec.rb +93 -0
- data/spec/spec_helper.rb +3 -1
- data/tasks/reek.rake +1 -10
- data/tasks/rspec.rake +16 -35
- metadata +43 -46
- data/lib/reek/checker.rb +0 -66
- data/lib/reek/class_checker.rb +0 -25
- data/lib/reek/file_checker.rb +0 -20
- data/lib/reek/method_checker.rb +0 -198
- data/lib/reek/printer.rb +0 -154
- data/lib/reek/smells/smell.rb +0 -56
- data/lib/reek/version.rb +0 -9
- data/setup.rb +0 -1585
- data/spec/integration_spec.rb +0 -30
- data/spec/reek/class_checker_spec.rb +0 -48
- data/spec/reek/method_checker_spec.rb +0 -67
- data/spec/reek/printer_spec.rb +0 -30
- data/spec/reek_source_spec.rb +0 -12
- data/spec/samples/inline.reek +0 -27
- data/spec/samples/optparse.reek +0 -79
- data/spec/samples/optparse/date.rb +0 -17
- data/spec/samples/optparse/shellwords.rb +0 -6
- data/spec/samples/optparse/time.rb +0 -10
- data/spec/samples/optparse/uri.rb +0 -6
- data/spec/samples/optparse/version.rb +0 -70
- data/spec/samples/redcloth.reek +0 -65
- data/tasks/samples.rake +0 -17
- data/website/index.html +0 -71
- data/website/index.txt +0 -40
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -138
- data/website/template.rhtml +0 -48
data/lib/reek/report.rb
CHANGED
@@ -1,52 +1,41 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
1
|
require 'set'
|
2
|
+
require 'reek/smells/smell_detector'
|
4
3
|
|
5
4
|
module Reek
|
6
|
-
|
7
|
-
class SortByContext
|
8
|
-
def self.compare(smell1, smell2)
|
9
|
-
smell1.detailed_report <=> smell2.detailed_report
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class SortBySmell
|
14
|
-
def self.compare(smell1, smell2)
|
15
|
-
smell1.report <=> smell2.report
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
5
|
class Report
|
20
|
-
|
21
|
-
SORT_ORDERS = {
|
22
|
-
:context => SortByContext,
|
23
|
-
:smell => SortBySmell
|
24
|
-
}
|
6
|
+
include Enumerable
|
25
7
|
|
26
8
|
def initialize # :nodoc:
|
27
|
-
@
|
9
|
+
@report = SortedSet.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
@report.each { |smell| yield smell }
|
28
14
|
end
|
29
15
|
|
30
16
|
def <<(smell) # :nodoc:
|
31
|
-
@
|
17
|
+
@report << smell
|
18
|
+
true
|
32
19
|
end
|
33
20
|
|
34
21
|
def empty? # :nodoc:
|
35
|
-
@
|
22
|
+
@report.empty?
|
36
23
|
end
|
37
24
|
|
38
25
|
def length # :nodoc:
|
39
|
-
@
|
26
|
+
@report.length
|
40
27
|
end
|
28
|
+
|
29
|
+
alias size length
|
41
30
|
|
42
31
|
def [](index) # :nodoc:
|
43
|
-
@
|
32
|
+
@report.to_a[index]
|
44
33
|
end
|
45
34
|
|
46
|
-
# Creates a formatted report of all the
|
35
|
+
# Creates a formatted report of all the +Smells::SmellWarning+ objects recorded in
|
47
36
|
# this report.
|
48
37
|
def to_s
|
49
|
-
@
|
38
|
+
@report.map {|smell| smell.report}.join("\n")
|
50
39
|
end
|
51
40
|
end
|
52
41
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Reek
|
2
|
+
class SexpFormatter
|
3
|
+
def self.format(sexp)
|
4
|
+
return sexp.to_s unless Array === sexp
|
5
|
+
first = sexp[1]
|
6
|
+
case sexp[0]
|
7
|
+
when :array
|
8
|
+
format_all(sexp, ', ')
|
9
|
+
when :call
|
10
|
+
meth, args = sexp[2..3]
|
11
|
+
result = format(first)
|
12
|
+
if meth.to_s == '[]'
|
13
|
+
result += (args.nil? ? '[]' : "[#{format(args)}]")
|
14
|
+
else
|
15
|
+
result += ".#{meth}" + (args ? "(#{format(args)})" : '')
|
16
|
+
end
|
17
|
+
result
|
18
|
+
when :colon2
|
19
|
+
format_all(sexp, '::')
|
20
|
+
when :const, :cvar, :dvar
|
21
|
+
format(first)
|
22
|
+
when :dot2
|
23
|
+
format_all(sexp, '..')
|
24
|
+
when :dstr
|
25
|
+
'"' + format_all(sexp, '') + '"'
|
26
|
+
when :evstr
|
27
|
+
"\#\{#{format(first)}\}"
|
28
|
+
when :fcall, :vcall
|
29
|
+
args = sexp[2]
|
30
|
+
result = first.to_s
|
31
|
+
result += "(#{format(args)})" if args
|
32
|
+
result
|
33
|
+
when :iter
|
34
|
+
'block'
|
35
|
+
when :lasgn
|
36
|
+
format_all(sexp, '=')
|
37
|
+
when :nth_ref
|
38
|
+
"$#{first}"
|
39
|
+
when :str
|
40
|
+
first
|
41
|
+
when :xstr
|
42
|
+
"`#{first}`"
|
43
|
+
else
|
44
|
+
sexp[-1].to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.format_all(sexp, glue)
|
49
|
+
sexp[1..-1].map {|arg| format(arg)}.join(glue)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'reek/name'
|
2
|
+
require 'reek/method_context'
|
3
|
+
require 'reek/sexp_formatter'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
class SingletonMethodContext < MethodContext
|
7
|
+
|
8
|
+
def initialize(outer, exp)
|
9
|
+
super(outer, exp, false)
|
10
|
+
@name = Name.new(exp[2])
|
11
|
+
@receiver = SexpFormatter.format(exp[1])
|
12
|
+
record_depends_on_self
|
13
|
+
end
|
14
|
+
|
15
|
+
def envious_receivers
|
16
|
+
[]
|
17
|
+
end
|
18
|
+
|
19
|
+
def outer_name
|
20
|
+
"#{@outer.outer_name}#{@receiver}.#{@name}/"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{@outer.outer_name}#{@receiver}.#{@name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'reek/options'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
|
5
|
+
#
|
6
|
+
# Reports a warning that a smell has been found.
|
7
|
+
#
|
8
|
+
class SmellWarning
|
9
|
+
include Comparable
|
10
|
+
|
11
|
+
def initialize(smell, context, warning)
|
12
|
+
@smell = smell
|
13
|
+
@context = context
|
14
|
+
@warning = warning
|
15
|
+
end
|
16
|
+
|
17
|
+
def hash # :nodoc:
|
18
|
+
report.hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
report <=> other.report
|
23
|
+
end
|
24
|
+
|
25
|
+
alias eql? <=> # :nodoc:
|
26
|
+
|
27
|
+
#
|
28
|
+
# Returns +true+ only if this is a warning about an instance of
|
29
|
+
# +smell_class+ and its report string matches all of the +patterns+.
|
30
|
+
#
|
31
|
+
def matches?(smell_class, patterns)
|
32
|
+
return false unless smell_class.to_s == @smell.class.class_name
|
33
|
+
rpt = report
|
34
|
+
return patterns.all? {|exp| exp === rpt}
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Returns a copy of the current report format (see +Options+)
|
39
|
+
# in which the following magic tokens have been substituted:
|
40
|
+
#
|
41
|
+
# * %s <-- the name of the smell that was detected
|
42
|
+
# * %c <-- a description of the +CodeContext+ containing the smell
|
43
|
+
# * %w <-- the specific problem that was detected
|
44
|
+
#
|
45
|
+
def report
|
46
|
+
Options[:format].gsub(/\%s/, @smell.smell_name).gsub(/\%c/, @context.to_s).gsub(/\%w/, @warning)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'reek/
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
require 'reek/sexp_formatter'
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Smells
|
@@ -33,21 +33,29 @@ module Reek
|
|
33
33
|
# method probably has more than one responsibility,
|
34
34
|
# because it includes at least two different code paths.
|
35
35
|
#
|
36
|
-
class ControlCouple <
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
class ControlCouple < SmellDetector
|
37
|
+
|
38
|
+
def self.contexts # :nodoc:
|
39
|
+
[:if]
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
43
|
-
|
44
|
-
cond[0] == :lvar and @args.include?(@couple[1])
|
42
|
+
def self.default_config
|
43
|
+
super.adopt(EXCLUDE_KEY => ['initialize'])
|
45
44
|
end
|
46
45
|
|
47
|
-
def
|
48
|
-
|
46
|
+
def initialize(config = ControlCouple.default_config)
|
47
|
+
super
|
49
48
|
end
|
50
|
-
end
|
51
49
|
|
50
|
+
#
|
51
|
+
# Checks whether the given conditional statement relies on a control couple.
|
52
|
+
# Any smells found are added to the +report+.
|
53
|
+
#
|
54
|
+
def examine_context(cond, report)
|
55
|
+
return unless cond.tests_a_parameter?
|
56
|
+
report << SmellWarning.new(self, cond,
|
57
|
+
"is controlled by argument #{SexpFormatter.format(cond.if_expr)}")
|
58
|
+
end
|
59
|
+
end
|
52
60
|
end
|
53
61
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'reek/
|
4
|
-
require 'reek/printer'
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
require 'reek/sexp_formatter'
|
5
4
|
|
6
5
|
module Reek
|
7
6
|
module Smells
|
@@ -19,36 +18,33 @@ module Reek
|
|
19
18
|
# @other.thing + @other.thing
|
20
19
|
# end
|
21
20
|
#
|
22
|
-
class Duplication <
|
21
|
+
class Duplication < SmellDetector
|
23
22
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
# and false otherwise.
|
28
|
-
#
|
29
|
-
def self.examine(method, report)
|
30
|
-
look_for_duplicate_calls(method, report)
|
31
|
-
end
|
23
|
+
# The name of the config field that sets the maximum number of
|
24
|
+
# identical calls to be permitted within any single method.
|
25
|
+
MAX_ALLOWED_CALLS_KEY = 'max_calls'
|
32
26
|
|
33
|
-
def self.
|
34
|
-
|
35
|
-
method.calls.select {|key,val| val > 1}.each do |call_exp|
|
36
|
-
call = call_exp[0]
|
37
|
-
report << new(method, call_exp[0]) unless call[2] == :new
|
38
|
-
smell_reported = true
|
39
|
-
end
|
40
|
-
return smell_reported
|
27
|
+
def self.default_config
|
28
|
+
super.adopt(MAX_ALLOWED_CALLS_KEY => 1)
|
41
29
|
end
|
42
30
|
|
43
|
-
def initialize(
|
44
|
-
super
|
45
|
-
@
|
31
|
+
def initialize(config = Duplication.default_config)
|
32
|
+
super
|
33
|
+
@max_calls = config[MAX_ALLOWED_CALLS_KEY]
|
46
34
|
end
|
47
35
|
|
48
|
-
def
|
49
|
-
|
36
|
+
def examine_context(method, report)
|
37
|
+
smelly_calls(method).each do |call|
|
38
|
+
report << SmellWarning.new(self, method,
|
39
|
+
"calls #{SexpFormatter.format(call)} multiple times")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def smelly_calls(method) # :nodoc:
|
44
|
+
method.calls.select do |key,val|
|
45
|
+
val > @max_calls and key[2] != :new
|
46
|
+
end.map { |call_exp| call_exp[0] }
|
50
47
|
end
|
51
48
|
end
|
52
|
-
|
53
49
|
end
|
54
50
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'reek/
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
require 'reek/sexp_formatter'
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Smells
|
@@ -32,34 +32,27 @@ module Reek
|
|
32
32
|
# Currently +FeatureEnvy+ reports any method that refers to self less
|
33
33
|
# often than it refers to (ie. send messages to) some other object.
|
34
34
|
#
|
35
|
-
class FeatureEnvy <
|
35
|
+
class FeatureEnvy < SmellDetector
|
36
|
+
|
37
|
+
def self.default_config
|
38
|
+
super.adopt(EXCLUDE_KEY => ['initialize'])
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(config = FeatureEnvy.default_config)
|
42
|
+
super
|
43
|
+
end
|
36
44
|
|
37
45
|
#
|
38
|
-
# Checks whether the given +
|
46
|
+
# Checks whether the given +context+ includes any code fragment that
|
39
47
|
# might "belong" on another class.
|
40
|
-
# Any smells found are added to the +report
|
41
|
-
# and false otherwise.
|
48
|
+
# Any smells found are added to the +report+.
|
42
49
|
#
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
method.refs.max_keys.each do |r|
|
48
|
-
report << new(method, Printer.print(r))
|
49
|
-
smell_found = true
|
50
|
+
def examine_context(context, report)
|
51
|
+
context.envious_receivers.each do |ref|
|
52
|
+
report << SmellWarning.new(self, context,
|
53
|
+
"refers to #{SexpFormatter.format(ref)} more than self")
|
50
54
|
end
|
51
|
-
smell_found
|
52
|
-
end
|
53
|
-
|
54
|
-
def initialize(context, receiver)
|
55
|
-
super(context)
|
56
|
-
@receiver = receiver
|
57
|
-
end
|
58
|
-
|
59
|
-
def detailed_report
|
60
|
-
"#{@context} refers to #{@receiver} more than self"
|
61
55
|
end
|
62
56
|
end
|
63
|
-
|
64
57
|
end
|
65
58
|
end
|
@@ -1,6 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'reek/smells/smell'
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
4
3
|
|
5
4
|
module Reek
|
6
5
|
module Smells
|
@@ -9,27 +8,43 @@ module Reek
|
|
9
8
|
# A Large Class is a class or module that has a large number of
|
10
9
|
# instance variables, methods or lines of code.
|
11
10
|
#
|
12
|
-
# Currently +LargeClass+ only reports classes having more than
|
13
|
-
#
|
11
|
+
# Currently +LargeClass+ only reports classes having more than a
|
12
|
+
# configurable number of methods. This includes public, protected and
|
13
|
+
# private methods, but excludes methods inherited from superclasses or
|
14
|
+
# included modules.
|
14
15
|
#
|
15
|
-
class LargeClass <
|
16
|
-
|
16
|
+
class LargeClass < SmellDetector
|
17
|
+
|
18
|
+
# The name of the config field that sets the maximum number of methods
|
19
|
+
# permitted in a class.
|
20
|
+
MAX_ALLOWED_METHODS_KEY = 'max_methods'
|
17
21
|
|
18
|
-
def self.
|
19
|
-
|
20
|
-
klass.instance_methods - klass.superclass.instance_methods
|
22
|
+
def self.contexts # :nodoc:
|
23
|
+
[:class]
|
21
24
|
end
|
22
25
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
26
|
+
def self.default_config
|
27
|
+
super.adopt(
|
28
|
+
MAX_ALLOWED_METHODS_KEY => 25,
|
29
|
+
EXCLUDE_KEY => ['Array', 'Hash', 'Module', 'String']
|
30
|
+
)
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
30
|
-
|
33
|
+
def initialize(config = LargeClass.default_config)
|
34
|
+
super
|
35
|
+
@max_methods = config[MAX_ALLOWED_METHODS_KEY]
|
31
36
|
end
|
32
|
-
end
|
33
37
|
|
38
|
+
#
|
39
|
+
# Checks the length of the given +klass+.
|
40
|
+
# Any smells found are added to the +report+.
|
41
|
+
#
|
42
|
+
def examine_context(klass, report)
|
43
|
+
num_methods = klass.num_methods
|
44
|
+
return false if num_methods <= @max_methods
|
45
|
+
report << SmellWarning.new(self, klass,
|
46
|
+
"has at least #{num_methods} methods")
|
47
|
+
end
|
48
|
+
end
|
34
49
|
end
|
35
50
|
end
|