reek 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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,36 @@ module Reek
|
|
9
8
|
# A Long Method is any method that has a large number of lines.
|
10
9
|
#
|
11
10
|
# Currently +LongMethod+ reports any method with more than
|
12
|
-
#
|
11
|
+
# 5 statements.
|
13
12
|
#
|
14
|
-
class LongMethod <
|
13
|
+
class LongMethod < SmellDetector
|
15
14
|
|
16
|
-
|
15
|
+
# The name of the config field that sets the maximum number of
|
16
|
+
# statements permitted in any method.
|
17
|
+
MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
|
17
18
|
|
18
|
-
def self.
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def self.default_config
|
20
|
+
super.adopt(
|
21
|
+
MAX_ALLOWED_STATEMENTS_KEY => 5,
|
22
|
+
EXCLUDE_KEY => ['initialize']
|
23
|
+
)
|
22
24
|
end
|
23
25
|
|
24
|
-
def initialize(
|
25
|
-
super
|
26
|
-
@
|
26
|
+
def initialize(config = LongMethod.default_config)
|
27
|
+
super
|
28
|
+
@max_statements = config[MAX_ALLOWED_STATEMENTS_KEY]
|
27
29
|
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
+
#
|
32
|
+
# Checks the length of the given +method+.
|
33
|
+
# Any smells found are added to the +report+.
|
34
|
+
#
|
35
|
+
def examine_context(method, report)
|
36
|
+
num = method.num_statements
|
37
|
+
return false if num <= @max_statements
|
38
|
+
report << SmellWarning.new(self, method,
|
39
|
+
"has approx #{num} statements")
|
31
40
|
end
|
32
41
|
end
|
33
|
-
|
34
42
|
end
|
35
43
|
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
|
@@ -10,27 +9,35 @@ module Reek
|
|
10
9
|
# or two parameters, or when a method yields more than one or
|
11
10
|
# two objects to an associated block.
|
12
11
|
#
|
13
|
-
# Currently +LongParameterList+ reports any method with
|
14
|
-
#
|
12
|
+
# Currently +LongParameterList+ reports any method or block with too
|
13
|
+
# many parameters.
|
15
14
|
#
|
16
|
-
class LongParameterList <
|
17
|
-
MAX_ALLOWED = 3
|
15
|
+
class LongParameterList < SmellDetector
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
result
|
23
|
-
end
|
17
|
+
# The name of the config field that sets the maximum number of
|
18
|
+
# parameters permitted in any method or block.
|
19
|
+
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
24
20
|
|
25
|
-
def
|
26
|
-
|
27
|
-
@num_params > MAX_ALLOWED
|
21
|
+
def self.default_config
|
22
|
+
super.adopt(MAX_ALLOWED_PARAMS_KEY => 3)
|
28
23
|
end
|
29
24
|
|
30
|
-
def
|
31
|
-
|
25
|
+
def initialize(config)
|
26
|
+
super
|
27
|
+
@max_params = config['max_params']
|
28
|
+
@action = 'has'
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Checks the number of parameters in the given scope.
|
33
|
+
# Any smells found are added to the +report+.
|
34
|
+
#
|
35
|
+
def examine_context(ctx, report)
|
36
|
+
num_params = ctx.parameters.length
|
37
|
+
return false if num_params <= @max_params
|
38
|
+
report << SmellWarning.new(self, ctx,
|
39
|
+
"#{@action} #{num_params} parameters")
|
32
40
|
end
|
33
41
|
end
|
34
|
-
|
35
42
|
end
|
36
43
|
end
|
@@ -1,20 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'reek/smells/smell'
|
1
|
+
require 'reek/smells/smell_detector'
|
4
2
|
|
5
3
|
module Reek
|
6
4
|
module Smells
|
7
5
|
|
8
6
|
class LongYieldList < LongParameterList
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
|
8
|
+
def self.contexts # :nodoc:
|
9
|
+
[:yield]
|
12
10
|
end
|
13
11
|
|
14
|
-
def
|
15
|
-
|
12
|
+
def initialize(config)
|
13
|
+
super
|
14
|
+
@action = 'yields'
|
16
15
|
end
|
17
16
|
end
|
18
|
-
|
19
17
|
end
|
20
18
|
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
|
@@ -10,15 +9,20 @@ module Reek
|
|
10
9
|
#
|
11
10
|
# +NestedIterators+ reports failing methods only once.
|
12
11
|
#
|
13
|
-
class NestedIterators <
|
14
|
-
|
15
|
-
|
12
|
+
class NestedIterators < SmellDetector
|
13
|
+
|
14
|
+
def self.contexts # :nodoc:
|
15
|
+
[:iter]
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
#
|
19
|
+
# Checks whether the given +block+ is inside another.
|
20
|
+
# Any smells found are added to the +report+.
|
21
|
+
#
|
22
|
+
def examine_context(block, report)
|
23
|
+
return false unless block.nested_block?
|
24
|
+
report << SmellWarning.new(self, block, 'is nested')
|
20
25
|
end
|
21
26
|
end
|
22
|
-
|
23
27
|
end
|
24
28
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Class
|
2
|
+
def name_words
|
3
|
+
class_name = name.split(/::/)[-1]
|
4
|
+
class_name.gsub(/([a-z])([A-Z])/) { |sub| "#{$1} #{$2}"}.split
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Reek
|
9
|
+
module Smells
|
10
|
+
|
11
|
+
class SmellDetector
|
12
|
+
|
13
|
+
# The name of the config field that lists the names of code contexts
|
14
|
+
# that should not be checked. Add this field to the config for each
|
15
|
+
# smell that should ignore this code element.
|
16
|
+
EXCLUDE_KEY = 'exclude'
|
17
|
+
|
18
|
+
# The name fo the config field that specifies whether a smell is
|
19
|
+
# enabled. Set to +true+ or +false+.
|
20
|
+
ENABLED_KEY = 'enabled'
|
21
|
+
|
22
|
+
def self.class_name
|
23
|
+
self.name.split(/::/)[-1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.contexts # :nodoc:
|
27
|
+
[:defn, :defs]
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.default_config
|
31
|
+
{
|
32
|
+
ENABLED_KEY => true,
|
33
|
+
EXCLUDE_KEY => []
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.listen(hooks, config)
|
38
|
+
detector = new(config[class_name])
|
39
|
+
contexts.each { |ctx| hooks[ctx] << detector }
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(config)
|
43
|
+
@enabled = config[ENABLED_KEY]
|
44
|
+
@exceptions = config[EXCLUDE_KEY]
|
45
|
+
end
|
46
|
+
|
47
|
+
def examine(context, report)
|
48
|
+
before = report.size
|
49
|
+
examine_context(context, report) if @enabled and !exception?(context)
|
50
|
+
report.length > before
|
51
|
+
end
|
52
|
+
|
53
|
+
def examine_context(context, report)
|
54
|
+
end
|
55
|
+
|
56
|
+
def exception?(context)
|
57
|
+
return false if @exceptions.nil? or @exceptions.length == 0
|
58
|
+
context.matches?(@exceptions)
|
59
|
+
end
|
60
|
+
|
61
|
+
def smell_name
|
62
|
+
self.class.name_words.join(' ')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/reek/smells/smells.rb
CHANGED
@@ -1,24 +1,85 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
1
|
require 'reek/smells/control_couple'
|
4
2
|
require 'reek/smells/duplication'
|
5
3
|
require 'reek/smells/feature_envy'
|
4
|
+
require 'reek/smells/large_class'
|
6
5
|
require 'reek/smells/long_method'
|
7
6
|
require 'reek/smells/long_parameter_list'
|
8
7
|
require 'reek/smells/long_yield_list'
|
9
8
|
require 'reek/smells/nested_iterators'
|
10
9
|
require 'reek/smells/uncommunicative_name'
|
11
10
|
require 'reek/smells/utility_function'
|
11
|
+
require 'yaml'
|
12
12
|
|
13
|
-
|
13
|
+
class Hash
|
14
|
+
def value_merge!(other)
|
15
|
+
other.keys.each do |key|
|
16
|
+
self[key].adopt!(other[key])
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
14
20
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
21
|
+
def adopt!(other)
|
22
|
+
other.keys.each do |key|
|
23
|
+
ov = other[key]
|
24
|
+
if Array === ov and has_key?(key)
|
25
|
+
self[key] += ov
|
26
|
+
else
|
27
|
+
self[key] = ov
|
28
|
+
end
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def adopt(other)
|
34
|
+
self.deep_copy.adopt!(other)
|
35
|
+
end
|
36
|
+
|
37
|
+
def deep_copy
|
38
|
+
YAML::load(YAML::dump(self))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Reek
|
43
|
+
class SmellConfig
|
44
|
+
|
45
|
+
SMELL_CLASSES = [
|
46
|
+
Smells::ControlCouple,
|
19
47
|
Smells::Duplication,
|
48
|
+
Smells::FeatureEnvy,
|
49
|
+
Smells::LargeClass,
|
50
|
+
Smells::LongMethod,
|
51
|
+
Smells::LongParameterList,
|
52
|
+
Smells::LongYieldList,
|
53
|
+
Smells::NestedIterators,
|
54
|
+
Smells::UncommunicativeName,
|
20
55
|
Smells::UtilityFunction,
|
21
|
-
|
22
|
-
|
23
|
-
|
56
|
+
]
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
defaults_file = File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'defaults.reek')
|
60
|
+
@config = YAML.load_file(defaults_file)
|
61
|
+
end
|
62
|
+
|
63
|
+
def smell_listeners()
|
64
|
+
result = Hash.new {|hash,key| hash[key] = [] }
|
65
|
+
SMELL_CLASSES.each { |smell| smell.listen(result, @config) }
|
66
|
+
return result
|
67
|
+
end
|
68
|
+
|
69
|
+
def load_local(file)
|
70
|
+
path = File.expand_path(file)
|
71
|
+
all_reekfiles(path).each do |rfile|
|
72
|
+
cf = YAML.load_file(rfile)
|
73
|
+
@config.value_merge!(cf)
|
74
|
+
end
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def all_reekfiles(path)
|
79
|
+
return [] unless File.exist?(path)
|
80
|
+
parent = File.dirname(path)
|
81
|
+
return [] if path == parent
|
82
|
+
all_reekfiles(parent) + Dir["#{path}/*.reek"]
|
83
|
+
end
|
84
|
+
end
|
24
85
|
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
|
@@ -18,55 +17,64 @@ module Reek
|
|
18
17
|
# * 1-character names
|
19
18
|
# * names consisting of a single character followed by a number
|
20
19
|
#
|
21
|
-
class UncommunicativeName <
|
20
|
+
class UncommunicativeName < SmellDetector
|
21
|
+
|
22
|
+
# The name of the config field that lists the regexps of
|
23
|
+
# smelly names to be rejected.
|
24
|
+
REJECT_KEY = 'reject'
|
25
|
+
|
26
|
+
# The name of the config field that lists the specific names that are
|
27
|
+
# to be treated as exceptions; these names will not be reported as
|
28
|
+
# uncommunicative.
|
29
|
+
ACCEPT_KEY = 'accept'
|
30
|
+
|
31
|
+
def self.default_config
|
32
|
+
super.adopt(
|
33
|
+
REJECT_KEY => [/^.[0-9]*$/],
|
34
|
+
ACCEPT_KEY => ['Inline::C']
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.contexts # :nodoc:
|
39
|
+
[:module, :class, :defn, :defs, :iter]
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(config = UncommunicativeName.default_config)
|
43
|
+
super
|
44
|
+
@reject = config[REJECT_KEY]
|
45
|
+
@accept = config[ACCEPT_KEY]
|
46
|
+
end
|
22
47
|
|
23
48
|
#
|
24
|
-
# Checks the given +
|
25
|
-
#
|
26
|
-
# Any smells found are added to the +report+; returns true in that case,
|
27
|
-
# and false otherwise.
|
49
|
+
# Checks the given +context+ for uncommunicative names.
|
50
|
+
# Any smells found are added to the +report+.
|
28
51
|
#
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
smell_reported = consider(param, method, report, 'parameter') || smell_reported
|
33
|
-
end
|
34
|
-
method.local_variables.each do |lvar|
|
35
|
-
smell_reported = consider(lvar, method, report, 'local variable') || smell_reported
|
36
|
-
end
|
37
|
-
method.instance_variables.each do |ivar|
|
38
|
-
smell_reported = consider(ivar, method, report, 'field') || smell_reported
|
39
|
-
end
|
40
|
-
smell_reported
|
52
|
+
def examine_context(context, report)
|
53
|
+
consider_name(context, report)
|
54
|
+
consider_variables(context, report)
|
41
55
|
end
|
42
56
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
report << new(
|
47
|
-
|
57
|
+
def consider_variables(context, report) # :nodoc:
|
58
|
+
context.variable_names.each do |name|
|
59
|
+
next unless is_bad_name?(name)
|
60
|
+
report << SmellWarning.new(self, context,
|
61
|
+
"has the variable name '#{name}'")
|
48
62
|
end
|
49
|
-
return false
|
50
63
|
end
|
51
64
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
return
|
56
|
-
|
57
|
-
|
65
|
+
def consider_name(context, report) # :nodoc:
|
66
|
+
name = context.name
|
67
|
+
return false if @accept.include?(context.to_s) # TODO: fq_name() ?
|
68
|
+
return false unless is_bad_name?(name)
|
69
|
+
report << SmellWarning.new(self, context,
|
70
|
+
"has the name '#{name}'")
|
58
71
|
end
|
59
72
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
@
|
64
|
-
end
|
65
|
-
|
66
|
-
def detailed_report
|
67
|
-
"#{@context} uses the #{@symbol_type} name '#{@bad_name}'"
|
73
|
+
def is_bad_name?(name) # :nodoc:
|
74
|
+
var = name.effective_name
|
75
|
+
return false if var == '*' or @accept.include?(var)
|
76
|
+
@reject.detect {|patt| patt === var}
|
68
77
|
end
|
69
78
|
end
|
70
|
-
|
71
79
|
end
|
72
80
|
end
|