kevinrutherford-reek 0.3.1.4
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 +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
|