kevinrutherford-reek 0.3.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +92 -0
- data/README.txt +6 -0
- data/Rakefile +7 -0
- data/bin/reek +19 -0
- 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 +53 -0
- data/lib/reek/options.rb +92 -0
- data/lib/reek/rake_task.rb +121 -0
- data/lib/reek/report.rb +42 -0
- 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 +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 +50 -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 +85 -0
- data/lib/reek/smells/uncommunicative_name.rb +80 -0
- data/lib/reek/smells/utility_function.rb +34 -0
- data/lib/reek/source.rb +116 -0
- data/lib/reek/spec.rb +130 -0
- data/lib/reek/stop_context.rb +62 -0
- data/lib/reek/yield_call_context.rb +14 -0
- data/lib/reek.rb +8 -0
- data/spec/integration/reek_source_spec.rb +20 -0
- data/spec/integration/script_spec.rb +55 -0
- 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/module_context_spec.rb +38 -0
- data/spec/reek/object_refs_spec.rb +129 -0
- data/spec/reek/options_spec.rb +13 -0
- data/spec/reek/report_spec.rb +48 -0
- 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 +23 -0
- data/spec/reek/smells/duplication_spec.rb +81 -0
- data/spec/reek/smells/feature_envy_spec.rb +129 -0
- data/spec/reek/smells/large_class_spec.rb +86 -0
- data/spec/reek/smells/long_method_spec.rb +59 -0
- data/spec/reek/smells/long_parameter_list_spec.rb +92 -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 +118 -0
- data/spec/reek/smells/utility_function_spec.rb +96 -0
- data/spec/samples/inline.rb +704 -0
- data/spec/samples/inline_spec.rb +40 -0
- data/spec/samples/optparse.rb +1788 -0
- data/spec/samples/optparse_spec.rb +100 -0
- data/spec/samples/redcloth.rb +1130 -0
- data/spec/samples/redcloth_spec.rb +93 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +15 -0
- data/tasks/reek.rake +20 -0
- data/tasks/rspec.rake +22 -0
- metadata +167 -0
@@ -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,85 @@
|
|
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 value_merge!(other)
|
15
|
+
other.keys.each do |key|
|
16
|
+
self[key].adopt!(other[key])
|
17
|
+
end
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
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,
|
47
|
+
Smells::Duplication,
|
48
|
+
Smells::FeatureEnvy,
|
49
|
+
Smells::LargeClass,
|
50
|
+
Smells::LongMethod,
|
51
|
+
Smells::LongParameterList,
|
52
|
+
Smells::LongYieldList,
|
53
|
+
Smells::NestedIterators,
|
54
|
+
Smells::UncommunicativeName,
|
55
|
+
Smells::UtilityFunction,
|
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
|
85
|
+
end
|
@@ -0,0 +1,80 @@
|
|
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
|
+
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
|
47
|
+
|
48
|
+
#
|
49
|
+
# Checks the given +context+ for uncommunicative names.
|
50
|
+
# Any smells found are added to the +report+.
|
51
|
+
#
|
52
|
+
def examine_context(context, report)
|
53
|
+
consider_name(context, report)
|
54
|
+
consider_variables(context, report)
|
55
|
+
end
|
56
|
+
|
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}'")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
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}'")
|
71
|
+
end
|
72
|
+
|
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}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
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
|
data/lib/reek/source.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'reek/code_parser'
|
2
|
+
require 'reek/report'
|
3
|
+
require 'reek/smells/smells'
|
4
|
+
|
5
|
+
module Reek
|
6
|
+
|
7
|
+
#
|
8
|
+
# A +Source+ object represents a chunk of Ruby source code.
|
9
|
+
#
|
10
|
+
# The various class methods are factories that will create +Source+
|
11
|
+
# instances from various types of input.
|
12
|
+
#
|
13
|
+
class Source
|
14
|
+
|
15
|
+
#
|
16
|
+
# Factory method: creates a +Source+ object by reading Ruby code from
|
17
|
+
# the +IO+ stream. The stream is consumed upto end-of-file, but the
|
18
|
+
# source code is not parsed until +report+ is called. +desc+ provides
|
19
|
+
# a string description to be used in the header of formatted reports.
|
20
|
+
#
|
21
|
+
def self.from_io(ios, desc)
|
22
|
+
code = ios.readlines.join
|
23
|
+
return new(code, desc)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Factory method: creates a +Source+ object by reading Ruby code from
|
28
|
+
# the +code+ string. The code is not parsed until +report+ is called.
|
29
|
+
#
|
30
|
+
def self.from_s(code)
|
31
|
+
return new(code, 'string')
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Factory method: creates a +Source+ object by reading Ruby code from
|
36
|
+
# File +file+. The source code is not parsed until +report+ is called.
|
37
|
+
#
|
38
|
+
def self.from_f(file)
|
39
|
+
from_path(file.path)
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Factory method: creates a +Source+ object by reading Ruby code from
|
44
|
+
# the named file. The source code is not parsed until +report+ is called.
|
45
|
+
#
|
46
|
+
def self.from_path(filename)
|
47
|
+
code = IO.readlines(filename).join
|
48
|
+
return new(code, filename, File.dirname(filename))
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Factory method: creates a +Source+ object from an array of file paths.
|
53
|
+
# No source code is actually parsed until the report is accessed.
|
54
|
+
#
|
55
|
+
def self.from_pathlist(paths)
|
56
|
+
sources = paths.map {|path| Source.from_path(path) }
|
57
|
+
SourceList.new(sources)
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(code, desc, dir = '.') # :nodoc:
|
61
|
+
@source = code
|
62
|
+
@dir = dir
|
63
|
+
@desc = desc
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Returns a +Report+ listing the smells found in this source. The first
|
68
|
+
# call to +report+ parses the source code and constructs a list of
|
69
|
+
# +SmellWarning+s found; subsequent calls simply return this same list.
|
70
|
+
#
|
71
|
+
def report
|
72
|
+
unless @report
|
73
|
+
@report = Report.new
|
74
|
+
smells = SmellConfig.new.load_local(@dir).smell_listeners
|
75
|
+
CodeParser.new(@report, smells).check_source(@source)
|
76
|
+
end
|
77
|
+
@report
|
78
|
+
end
|
79
|
+
|
80
|
+
def smelly?
|
81
|
+
report.length > 0
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Checks this source for instances of +smell_class+, and returns +true+
|
86
|
+
# only if one of them has a report string matching all of the +patterns+.
|
87
|
+
#
|
88
|
+
def has_smell?(smell_class, patterns)
|
89
|
+
report.any? { |smell| smell.matches?(smell_class, patterns) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
@desc
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Represents a list of Sources as if they were a single source.
|
99
|
+
#
|
100
|
+
class SourceList
|
101
|
+
def initialize(sources)
|
102
|
+
@sources = sources
|
103
|
+
end
|
104
|
+
|
105
|
+
def smelly?
|
106
|
+
@sources.any? {|source| source.smelly? }
|
107
|
+
end
|
108
|
+
|
109
|
+
def report
|
110
|
+
@sources.select {|src| src.smelly? }.map do |src|
|
111
|
+
warnings = src.report
|
112
|
+
"\"#{src}\" -- #{warnings.length} warnings:\n#{warnings.to_s}\n"
|
113
|
+
end.join("\n")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/reek/spec.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'reek/source'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
|
5
|
+
#
|
6
|
+
# Provides matchers for Rspec, making it easy to check code quality.
|
7
|
+
#
|
8
|
+
# === Examples
|
9
|
+
#
|
10
|
+
# Here's a spec that ensures there are no smell warnings in the current project:
|
11
|
+
#
|
12
|
+
# describe 'source code quality' do
|
13
|
+
# Dir['lib/**/*.rb'].each do |path|
|
14
|
+
# it "reports no smells in #{path}" do
|
15
|
+
# File.new(path).should_not reek
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Here's a simple check of a code fragment:
|
21
|
+
#
|
22
|
+
# 'def equals(other) other.thing == self.thing end'.should_not reek
|
23
|
+
#
|
24
|
+
# And a more complex example, making use of one of the factory methods for
|
25
|
+
# +Source+ so that the code is parsed and analysed only once:
|
26
|
+
#
|
27
|
+
# ruby = 'def double_thing() @other.thing.foo + @other.thing.foo end'.to_source
|
28
|
+
# ruby.should reek_of(:Duplication, /@other.thing[^\.]/)
|
29
|
+
# ruby.should reek_of(:Duplication, /@other.thing.foo/)
|
30
|
+
# ruby.should_not reek_of(:FeatureEnvy)
|
31
|
+
#
|
32
|
+
module Spec
|
33
|
+
class ShouldReek # :nodoc:
|
34
|
+
def matches?(actual)
|
35
|
+
@source = actual.to_source
|
36
|
+
@source.smelly?
|
37
|
+
end
|
38
|
+
def failure_message_for_should
|
39
|
+
"Expected source to reek, but it didn't"
|
40
|
+
end
|
41
|
+
def failure_message_for_should_not
|
42
|
+
"Expected no smells, but got:\n#{@source.report}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Returns +true+ if and only if the target source code contains smells.
|
48
|
+
#
|
49
|
+
def reek
|
50
|
+
ShouldReek.new
|
51
|
+
end
|
52
|
+
|
53
|
+
class ShouldReekOf # :nodoc:
|
54
|
+
def initialize(klass, patterns)
|
55
|
+
@klass = klass
|
56
|
+
@patterns = patterns
|
57
|
+
end
|
58
|
+
def matches?(actual)
|
59
|
+
@source = actual.to_source
|
60
|
+
@source.has_smell?(@klass, @patterns)
|
61
|
+
end
|
62
|
+
def failure_message_for_should
|
63
|
+
"Expected #{@source} to reek of #{@klass}, but it didn't"
|
64
|
+
end
|
65
|
+
def failure_message_for_should_not
|
66
|
+
"Expected #{@source} not to reek of #{@klass}, but got:\n#{@source.report}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Checks the target source code for instances of +smell_class+,
|
72
|
+
# and returns +true+ only if one of them has a report string matching
|
73
|
+
# all of the +patterns+.
|
74
|
+
#
|
75
|
+
def reek_of(smell_class, *patterns)
|
76
|
+
ShouldReekOf.new(smell_class, patterns)
|
77
|
+
end
|
78
|
+
|
79
|
+
class ShouldReekOnlyOf # :nodoc:
|
80
|
+
def initialize(klass, patterns)
|
81
|
+
@klass = klass
|
82
|
+
@patterns = patterns
|
83
|
+
end
|
84
|
+
def matches?(actual)
|
85
|
+
@source = actual.to_source
|
86
|
+
@source.report.length == 1 and @source.has_smell?(@klass, @patterns)
|
87
|
+
end
|
88
|
+
def failure_message_for_should
|
89
|
+
"Expected source to reek only of #{@klass}, but got:\n#{@source.report}"
|
90
|
+
end
|
91
|
+
def failure_message_for_should_not
|
92
|
+
"Expected source not to reek only of #{@klass}, but it did"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# As for reek_of, but the matched smell warning must be the only warning of
|
98
|
+
# any kind in the target source code's Reek report.
|
99
|
+
#
|
100
|
+
def reek_only_of(smell_class, *patterns)
|
101
|
+
ShouldReekOnlyOf.new(smell_class, patterns)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class File
|
107
|
+
def to_source
|
108
|
+
Reek::Source.from_f(self)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class String
|
113
|
+
def to_source
|
114
|
+
Reek::Source.from_s(self)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Array
|
119
|
+
def to_source
|
120
|
+
Reek::Source.from_pathlist(self)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
module Reek
|
125
|
+
class Source
|
126
|
+
def to_source
|
127
|
+
self
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Reek
|
2
|
+
class StopContext
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@refs = ObjectRefs.new
|
6
|
+
@myself = Object
|
7
|
+
end
|
8
|
+
|
9
|
+
def count_statements(num)
|
10
|
+
0
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_module(name)
|
14
|
+
sym = name.to_s
|
15
|
+
@myself.const_defined?(sym) ? @myself.const_get(sym) : nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def has_parameter(sym)
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def inside_a_block?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def is_overriding_method?(name)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def num_statements
|
31
|
+
0
|
32
|
+
end
|
33
|
+
|
34
|
+
def refs
|
35
|
+
@refs
|
36
|
+
end
|
37
|
+
|
38
|
+
def record_depends_on_self
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def record_call_to(exp)
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def record_method(name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def record_parameter(sym)
|
50
|
+
end
|
51
|
+
|
52
|
+
def record_instance_variable(sym)
|
53
|
+
end
|
54
|
+
|
55
|
+
def record_local_variable(sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def outer_name
|
59
|
+
''
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|