teksymmetry-reek 1.1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +131 -0
- data/README.txt +36 -0
- data/Rakefile +17 -0
- data/bin/reek +27 -0
- data/config/defaults.reek +51 -0
- data/lib/reek/block_context.rb +59 -0
- data/lib/reek/class_context.rb +68 -0
- data/lib/reek/code_context.rb +54 -0
- data/lib/reek/code_parser.rb +221 -0
- data/lib/reek/exceptions.reek +13 -0
- data/lib/reek/if_context.rb +25 -0
- data/lib/reek/method_context.rb +91 -0
- data/lib/reek/module_context.rb +33 -0
- data/lib/reek/name.rb +49 -0
- data/lib/reek/object_refs.rb +52 -0
- data/lib/reek/object_source.rb +53 -0
- data/lib/reek/options.rb +100 -0
- data/lib/reek/rake_task.rb +121 -0
- data/lib/reek/report.rb +81 -0
- data/lib/reek/sexp_formatter.rb +10 -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 +61 -0
- data/lib/reek/smells/duplication.rb +50 -0
- data/lib/reek/smells/feature_envy.rb +58 -0
- data/lib/reek/smells/large_class.rb +69 -0
- data/lib/reek/smells/long_method.rb +43 -0
- data/lib/reek/smells/long_parameter_list.rb +43 -0
- data/lib/reek/smells/long_yield_list.rb +18 -0
- data/lib/reek/smells/nested_iterators.rb +28 -0
- data/lib/reek/smells/smell_detector.rb +66 -0
- data/lib/reek/smells/smells.rb +81 -0
- data/lib/reek/smells/uncommunicative_name.rb +97 -0
- data/lib/reek/smells/utility_function.rb +34 -0
- data/lib/reek/source.rb +127 -0
- data/lib/reek/spec.rb +146 -0
- data/lib/reek/stop_context.rb +50 -0
- data/lib/reek/yield_call_context.rb +12 -0
- data/lib/reek.rb +7 -0
- data/reek.gemspec +44 -0
- data/spec/reek/block_context_spec.rb +40 -0
- data/spec/reek/class_context_spec.rb +169 -0
- data/spec/reek/code_context_spec.rb +93 -0
- data/spec/reek/code_parser_spec.rb +34 -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 +66 -0
- data/spec/reek/module_context_spec.rb +38 -0
- data/spec/reek/name_spec.rb +13 -0
- data/spec/reek/object_refs_spec.rb +131 -0
- data/spec/reek/options_spec.rb +13 -0
- data/spec/reek/report_spec.rb +48 -0
- data/spec/reek/singleton_method_context_spec.rb +17 -0
- data/spec/reek/smells/control_couple_spec.rb +23 -0
- data/spec/reek/smells/duplication_spec.rb +81 -0
- data/spec/reek/smells/feature_envy_spec.rb +221 -0
- data/spec/reek/smells/large_class_spec.rb +87 -0
- data/spec/reek/smells/long_method_spec.rb +195 -0
- data/spec/reek/smells/long_parameter_list_spec.rb +85 -0
- data/spec/reek/smells/nested_iterators_spec.rb +33 -0
- data/spec/reek/smells/smell_spec.rb +24 -0
- data/spec/reek/smells/uncommunicative_name_spec.rb +123 -0
- data/spec/reek/smells/utility_function_spec.rb +93 -0
- data/spec/slow/inline_spec.rb +40 -0
- data/spec/slow/optparse_spec.rb +109 -0
- data/spec/slow/redcloth_spec.rb +101 -0
- data/spec/slow/reek_source_spec.rb +20 -0
- data/spec/slow/samples/inline.rb +704 -0
- data/spec/slow/samples/optparse.rb +1788 -0
- data/spec/slow/samples/redcloth.rb +1130 -0
- data/spec/slow/script_spec.rb +55 -0
- data/spec/slow/source_list_spec.rb +40 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +13 -0
- data/tasks/reek.rake +7 -0
- data/tasks/rspec.rake +22 -0
- metadata +163 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
require 'reek/sexp_formatter'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
module Smells
|
7
|
+
|
8
|
+
#
|
9
|
+
# Control Coupling occurs when a method or block checks the value of
|
10
|
+
# a parameter in order to decide which execution path to take. The
|
11
|
+
# offending parameter is often called a Control Couple.
|
12
|
+
#
|
13
|
+
# A simple example would be the <tt>quoted</tt> parameter
|
14
|
+
# in the following method:
|
15
|
+
#
|
16
|
+
# def write(quoted)
|
17
|
+
# if quoted
|
18
|
+
# write_quoted(@value)
|
19
|
+
# else
|
20
|
+
# puts @value
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Control Coupling is a kind of duplication, because the calling method
|
25
|
+
# already knows which path should be taken.
|
26
|
+
#
|
27
|
+
# Control Coupling reduces the code's flexibility by creating a
|
28
|
+
# dependency between the caller and callee:
|
29
|
+
# any change to the possible values of the controlling parameter must
|
30
|
+
# be reflected on both sides of the call.
|
31
|
+
#
|
32
|
+
# A Control Couple also reveals a loss of simplicity: the called
|
33
|
+
# method probably has more than one responsibility,
|
34
|
+
# because it includes at least two different code paths.
|
35
|
+
#
|
36
|
+
class ControlCouple < SmellDetector
|
37
|
+
|
38
|
+
def self.contexts # :nodoc:
|
39
|
+
[:if]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.default_config
|
43
|
+
super.adopt(EXCLUDE_KEY => ['initialize'])
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(config = ControlCouple.default_config)
|
47
|
+
super
|
48
|
+
end
|
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
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
require 'reek/sexp_formatter'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
module Smells
|
7
|
+
|
8
|
+
#
|
9
|
+
# Duplication occurs when two fragments of code look nearly identical,
|
10
|
+
# or when two fragments of code have nearly identical effects
|
11
|
+
# at some conceptual level.
|
12
|
+
#
|
13
|
+
# Currently +Duplication+ checks for repeated identical method calls
|
14
|
+
# within any one method definition. For example, the following method
|
15
|
+
# will report a warning:
|
16
|
+
#
|
17
|
+
# def double_thing()
|
18
|
+
# @other.thing + @other.thing
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
class Duplication < SmellDetector
|
22
|
+
|
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'
|
26
|
+
|
27
|
+
def self.default_config
|
28
|
+
super.adopt(MAX_ALLOWED_CALLS_KEY => 1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(config = Duplication.default_config)
|
32
|
+
super
|
33
|
+
@max_calls = config[MAX_ALLOWED_CALLS_KEY]
|
34
|
+
end
|
35
|
+
|
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] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
require 'reek/sexp_formatter'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
module Smells
|
7
|
+
|
8
|
+
#
|
9
|
+
# Feature Envy occurs when a code fragment references another object
|
10
|
+
# more often than it references itself, or when several clients do
|
11
|
+
# the same series of manipulations on a particular type of object.
|
12
|
+
#
|
13
|
+
# A simple example would be the following method, which "belongs"
|
14
|
+
# on the Item class and not on the Cart class:
|
15
|
+
#
|
16
|
+
# class Cart
|
17
|
+
# def price
|
18
|
+
# @item.price + @item.tax
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Feature Envy reduces the code's ability to communicate intent:
|
23
|
+
# code that "belongs" on one class but which is located in another
|
24
|
+
# can be hard to find, and may upset the "System of Names"
|
25
|
+
# in the host class.
|
26
|
+
#
|
27
|
+
# Feature Envy also affects the design's flexibility: A code fragment
|
28
|
+
# that is in the wrong class creates couplings that may not be natural
|
29
|
+
# within the application's domain, and creates a loss of cohesion
|
30
|
+
# in the unwilling host class.
|
31
|
+
#
|
32
|
+
# Currently +FeatureEnvy+ reports any method that refers to self less
|
33
|
+
# often than it refers to (ie. send messages to) some other object.
|
34
|
+
#
|
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
|
44
|
+
|
45
|
+
#
|
46
|
+
# Checks whether the given +context+ includes any code fragment that
|
47
|
+
# might "belong" on another class.
|
48
|
+
# Any smells found are added to the +report+.
|
49
|
+
#
|
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")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# A Large Class is a class or module that has a large number of
|
9
|
+
# instance variables, methods or lines of code.
|
10
|
+
#
|
11
|
+
# Currently +LargeClass+ only reports classes having more than a
|
12
|
+
# configurable number of methods or instance variables. The method count
|
13
|
+
# includes public, protected and
|
14
|
+
# private methods, and excludes methods inherited from superclasses or
|
15
|
+
# included modules.
|
16
|
+
#
|
17
|
+
class LargeClass < SmellDetector
|
18
|
+
|
19
|
+
# The name of the config field that sets the maximum number of methods
|
20
|
+
# permitted in a class.
|
21
|
+
MAX_ALLOWED_METHODS_KEY = 'max_methods'
|
22
|
+
|
23
|
+
# The name of the config field that sets the maximum number of instance
|
24
|
+
# variables permitted in a class.
|
25
|
+
MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
|
26
|
+
|
27
|
+
def self.contexts # :nodoc:
|
28
|
+
[:class]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.default_config
|
32
|
+
super.adopt(
|
33
|
+
MAX_ALLOWED_METHODS_KEY => 25,
|
34
|
+
MAX_ALLOWED_IVARS_KEY => 9,
|
35
|
+
EXCLUDE_KEY => []
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(config = LargeClass.default_config)
|
40
|
+
super
|
41
|
+
@max_methods = config[MAX_ALLOWED_METHODS_KEY]
|
42
|
+
@max_instance_variables = config[MAX_ALLOWED_IVARS_KEY]
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_num_methods(klass, report) # :nodoc:
|
46
|
+
count = klass.num_methods
|
47
|
+
return if count <= @max_methods
|
48
|
+
report << SmellWarning.new(self, klass,
|
49
|
+
"has at least #{count} methods")
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_num_ivars(klass, report) # :nodoc:
|
53
|
+
count = klass.variable_names.length
|
54
|
+
return if count <= @max_instance_variables
|
55
|
+
report << SmellWarning.new(self, klass,
|
56
|
+
"has at least #{count} instance variables")
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Checks +klass+ for too many methods or too many instance variables.
|
61
|
+
# Any smells found are added to the +report+.
|
62
|
+
#
|
63
|
+
def examine_context(klass, report)
|
64
|
+
check_num_methods(klass, report)
|
65
|
+
check_num_ivars(klass, report)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# A Long Method is any method that has a large number of lines.
|
9
|
+
#
|
10
|
+
# Currently +LongMethod+ reports any method with more than
|
11
|
+
# 5 statements.
|
12
|
+
#
|
13
|
+
class LongMethod < SmellDetector
|
14
|
+
|
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'
|
18
|
+
|
19
|
+
def self.default_config
|
20
|
+
super.adopt(
|
21
|
+
MAX_ALLOWED_STATEMENTS_KEY => 5,
|
22
|
+
EXCLUDE_KEY => ['initialize']
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(config = LongMethod.default_config)
|
27
|
+
super
|
28
|
+
@max_statements = config[MAX_ALLOWED_STATEMENTS_KEY]
|
29
|
+
end
|
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")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# A Long Parameter List occurs when a method has more than one
|
9
|
+
# or two parameters, or when a method yields more than one or
|
10
|
+
# two objects to an associated block.
|
11
|
+
#
|
12
|
+
# Currently +LongParameterList+ reports any method or block with too
|
13
|
+
# many parameters.
|
14
|
+
#
|
15
|
+
class LongParameterList < SmellDetector
|
16
|
+
|
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'
|
20
|
+
|
21
|
+
def self.default_config
|
22
|
+
super.adopt(MAX_ALLOWED_PARAMS_KEY => 3)
|
23
|
+
end
|
24
|
+
|
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")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Smells
|
5
|
+
|
6
|
+
class LongYieldList < LongParameterList
|
7
|
+
|
8
|
+
def self.contexts # :nodoc:
|
9
|
+
[:yield]
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
super
|
14
|
+
@action = 'yields'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# A Nested Iterator occurs when a block contains another block.
|
9
|
+
#
|
10
|
+
# +NestedIterators+ reports failing methods only once.
|
11
|
+
#
|
12
|
+
class NestedIterators < SmellDetector
|
13
|
+
|
14
|
+
def self.contexts # :nodoc:
|
15
|
+
[:iter]
|
16
|
+
end
|
17
|
+
|
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')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
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
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'reek/smells/control_couple'
|
2
|
+
require 'reek/smells/duplication'
|
3
|
+
require 'reek/smells/feature_envy'
|
4
|
+
require 'reek/smells/large_class'
|
5
|
+
require 'reek/smells/long_method'
|
6
|
+
require 'reek/smells/long_parameter_list'
|
7
|
+
require 'reek/smells/long_yield_list'
|
8
|
+
require 'reek/smells/nested_iterators'
|
9
|
+
require 'reek/smells/uncommunicative_name'
|
10
|
+
require 'reek/smells/utility_function'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
class Hash
|
14
|
+
def push_keys(hash)
|
15
|
+
keys.each {|key| hash[key].adopt!(self[key]) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def adopt!(other)
|
19
|
+
other.keys.each do |key|
|
20
|
+
ov = other[key]
|
21
|
+
if Array === ov and has_key?(key)
|
22
|
+
self[key] += ov
|
23
|
+
else
|
24
|
+
self[key] = ov
|
25
|
+
end
|
26
|
+
end
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def adopt(other)
|
31
|
+
self.deep_copy.adopt!(other)
|
32
|
+
end
|
33
|
+
|
34
|
+
def deep_copy
|
35
|
+
YAML::load(YAML::dump(self))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Reek
|
40
|
+
class SmellConfig
|
41
|
+
|
42
|
+
SMELL_CLASSES = [
|
43
|
+
Smells::ControlCouple,
|
44
|
+
Smells::Duplication,
|
45
|
+
Smells::FeatureEnvy,
|
46
|
+
Smells::LargeClass,
|
47
|
+
Smells::LongMethod,
|
48
|
+
Smells::LongParameterList,
|
49
|
+
Smells::LongYieldList,
|
50
|
+
Smells::NestedIterators,
|
51
|
+
Smells::UncommunicativeName,
|
52
|
+
Smells::UtilityFunction,
|
53
|
+
]
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
defaults_file = File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'defaults.reek')
|
57
|
+
@config = YAML.load_file(defaults_file)
|
58
|
+
end
|
59
|
+
|
60
|
+
def smell_listeners()
|
61
|
+
result = Hash.new {|hash,key| hash[key] = [] }
|
62
|
+
SMELL_CLASSES.each { |smell| smell.listen(result, @config) }
|
63
|
+
return result
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_local(file)
|
67
|
+
path = File.expand_path(file)
|
68
|
+
all_reekfiles(path).each do |rfile|
|
69
|
+
YAML.load_file(rfile).push_keys(@config)
|
70
|
+
end
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def all_reekfiles(path)
|
75
|
+
return [] unless File.exist?(path)
|
76
|
+
parent = File.dirname(path)
|
77
|
+
return [] if path == parent
|
78
|
+
all_reekfiles(parent) + Dir["#{path}/*.reek"]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# An Uncommunicative Name is a name that doesn't communicate its intent
|
9
|
+
# well enough.
|
10
|
+
#
|
11
|
+
# Poor names make it hard for the reader to build a mental picture
|
12
|
+
# of what's going on in the code. They can also be mis-interpreted;
|
13
|
+
# and they hurt the flow of reading, because the reader must slow
|
14
|
+
# down to interpret the names.
|
15
|
+
#
|
16
|
+
# Currently +UncommunicativeName+ checks for
|
17
|
+
# * 1-character names
|
18
|
+
# * names consisting of a single character followed by a number
|
19
|
+
#
|
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
|
+
# The name of the config field that list the verifier extention
|
32
|
+
# ruby scripts. This script contains a class which is used for verifying
|
33
|
+
# the variable with their associated context.
|
34
|
+
VERIFIER_EXTENSION_KEY = 'verifierExtention'
|
35
|
+
|
36
|
+
def self.default_config
|
37
|
+
super.adopt(
|
38
|
+
REJECT_KEY => [/^.[0-9]*$/],
|
39
|
+
ACCEPT_KEY => ['Inline::C']
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.contexts # :nodoc:
|
44
|
+
[:module, :class, :defn, :defs, :iter]
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(config = UncommunicativeName.default_config)
|
48
|
+
super
|
49
|
+
@reject = config[REJECT_KEY]
|
50
|
+
@accept = config[ACCEPT_KEY]
|
51
|
+
@verifier_extensions = config[VERIFIER_EXTENSION_KEY]
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Checks the given +context+ for uncommunicative names.
|
56
|
+
# Any smells found are added to the +report+.
|
57
|
+
#
|
58
|
+
def examine_context(context, report)
|
59
|
+
consider_name(context, report)
|
60
|
+
consider_variables(context, report)
|
61
|
+
end
|
62
|
+
|
63
|
+
def consider_variables(context, report) # :nodoc:
|
64
|
+
context.variable_names.each do |name|
|
65
|
+
|
66
|
+
report << SmellWarning.new(self, context,
|
67
|
+
"has the variable name '#{name}'") if is_bad_name?(name)
|
68
|
+
|
69
|
+
accepted, message = verifier_extension_accepted?(context, name)
|
70
|
+
if !accepted
|
71
|
+
report << SmellWarning.new(self, context, message)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def verifier_extension_accepted?(p_context, p_name)
|
77
|
+
return true, nil if !@verifier_extensions
|
78
|
+
accepted, message = VerifierExtensionManager::accepted?(@verifier_extensions, p_context, p_name)
|
79
|
+
return accepted, message
|
80
|
+
end
|
81
|
+
|
82
|
+
def consider_name(context, report) # :nodoc:
|
83
|
+
name = context.name
|
84
|
+
return false if @accept.include?(context.to_s) # TODO: fq_name() ?
|
85
|
+
return false unless is_bad_name?(name)
|
86
|
+
report << SmellWarning.new(self, context,
|
87
|
+
"has the name '#{name}'")
|
88
|
+
end
|
89
|
+
|
90
|
+
def is_bad_name?(name) # :nodoc:
|
91
|
+
var = name.effective_name
|
92
|
+
return false if var == '*' or @accept.include?(var)
|
93
|
+
@reject.detect {|patt| patt === var}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'reek/smells/smell_detector'
|
2
|
+
require 'reek/smell_warning'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Smells
|
6
|
+
|
7
|
+
#
|
8
|
+
# A Utility Function is any instance method that has no
|
9
|
+
# dependency on the state of the instance.
|
10
|
+
#
|
11
|
+
# Currently +UtilityFunction+ will warn about any method that:
|
12
|
+
#
|
13
|
+
# * is non-empty
|
14
|
+
# * does not override an inherited method
|
15
|
+
# * calls at least one method on another object
|
16
|
+
# * doesn't use any of self's instance variables
|
17
|
+
# * doesn't use any of self's methods
|
18
|
+
#
|
19
|
+
class UtilityFunction < SmellDetector
|
20
|
+
|
21
|
+
#
|
22
|
+
# Checks whether the given +method+ is a utility function.
|
23
|
+
# Any smells found are added to the +report+.
|
24
|
+
#
|
25
|
+
def examine_context(method, report)
|
26
|
+
return false if method.calls.keys.length == 0 or
|
27
|
+
method.num_statements == 0 or
|
28
|
+
method.depends_on_instance?
|
29
|
+
report << SmellWarning.new(self, method,
|
30
|
+
"doesn't depend on instance state")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|