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
@@ -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
|