reek 1.6.6 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -9
- data/features/command_line_interface/options.feature +20 -16
- data/features/command_line_interface/stdin.feature +1 -1
- data/features/rake_task/rake_task.feature +0 -12
- data/features/reports/reports.feature +63 -23
- data/features/reports/yaml.feature +3 -3
- data/features/samples.feature +3 -3
- data/lib/reek/cli/application.rb +5 -5
- data/lib/reek/cli/input.rb +1 -1
- data/lib/reek/cli/option_interpreter.rb +77 -0
- data/lib/reek/cli/options.rb +89 -82
- data/lib/reek/cli/report/formatter.rb +33 -24
- data/lib/reek/cli/report/heading_formatter.rb +45 -0
- data/lib/reek/cli/report/location_formatter.rb +23 -0
- data/lib/reek/cli/report/report.rb +32 -17
- data/lib/reek/configuration/app_configuration.rb +2 -2
- data/lib/reek/configuration/configuration_file_finder.rb +10 -10
- data/lib/reek/core/smell_repository.rb +3 -28
- data/lib/reek/rake/task.rb +35 -76
- data/lib/reek/smell_warning.rb +31 -16
- data/lib/reek/smells/nested_iterators.rb +1 -1
- data/lib/reek/smells/smell_detector.rb +9 -0
- data/lib/reek/smells/utility_function.rb +2 -1
- data/lib/reek/spec/should_reek.rb +0 -3
- data/lib/reek/spec/should_reek_of.rb +61 -12
- data/lib/reek/spec/should_reek_only_of.rb +12 -10
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +2 -2
- data/spec/factories/factories.rb +2 -5
- data/spec/reek/cli/html_report_spec.rb +28 -0
- data/spec/reek/cli/option_interperter_spec.rb +14 -0
- data/spec/reek/cli/text_report_spec.rb +95 -0
- data/spec/reek/cli/yaml_report_spec.rb +23 -0
- data/spec/reek/configuration/configuration_file_finder_spec.rb +5 -6
- data/spec/reek/core/module_context_spec.rb +1 -1
- data/spec/reek/core/smell_repository_spec.rb +17 -0
- data/spec/reek/smell_warning_spec.rb +9 -11
- data/spec/reek/smells/boolean_parameter_spec.rb +11 -11
- data/spec/reek/smells/control_parameter_spec.rb +40 -40
- data/spec/reek/smells/data_clump_spec.rb +17 -17
- data/spec/reek/smells/duplicate_method_call_spec.rb +56 -33
- data/spec/reek/smells/feature_envy_spec.rb +44 -40
- data/spec/reek/smells/irresponsible_module_spec.rb +1 -1
- data/spec/reek/smells/long_parameter_list_spec.rb +12 -12
- data/spec/reek/smells/long_yield_list_spec.rb +4 -4
- data/spec/reek/smells/module_initialize_spec.rb +3 -3
- data/spec/reek/smells/nested_iterators_spec.rb +71 -52
- data/spec/reek/smells/nil_check_spec.rb +6 -6
- data/spec/reek/smells/prima_donna_method_spec.rb +2 -2
- data/spec/reek/smells/too_many_statements_spec.rb +34 -34
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -1
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +7 -3
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +12 -12
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +28 -38
- data/spec/reek/smells/unused_parameters_spec.rb +16 -17
- data/spec/reek/smells/utility_function_spec.rb +21 -8
- data/spec/reek/spec/should_reek_of_spec.rb +18 -5
- data/spec/reek/spec/should_reek_only_of_spec.rb +7 -1
- data/spec/spec_helper.rb +22 -14
- metadata +15 -20
- data/lib/reek/cli/help_command.rb +0 -15
- data/lib/reek/cli/report/strategy.rb +0 -64
- data/lib/reek/cli/version_command.rb +0 -16
- data/spec/matchers/smell_of_matcher.rb +0 -95
- data/spec/reek/cli/help_command_spec.rb +0 -25
- data/spec/reek/cli/report_spec.rb +0 -132
- data/spec/reek/cli/version_command_spec.rb +0 -31
data/lib/reek/smell_warning.rb
CHANGED
@@ -34,36 +34,51 @@ module Reek
|
|
34
34
|
(self <=> other) == 0
|
35
35
|
end
|
36
36
|
|
37
|
-
def matches?(klass,
|
38
|
-
smell_classes.include?(klass.to_s) &&
|
37
|
+
def matches?(klass, other_parameters = {})
|
38
|
+
smell_classes.include?(klass.to_s) && common_parameters_equal?(other_parameters)
|
39
39
|
end
|
40
40
|
|
41
41
|
def report_on(listener)
|
42
42
|
listener.found_smell(self)
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
45
|
+
def yaml_hash
|
46
|
+
result = {
|
47
|
+
'smell_category' => smell_detector.smell_category,
|
48
|
+
'smell_type' => smell_detector.smell_type,
|
49
|
+
'source' => smell_detector.source,
|
50
|
+
'context' => context,
|
51
|
+
'lines' => lines,
|
52
|
+
'message' => message
|
53
|
+
}
|
53
54
|
parameters.each do |key, value|
|
54
|
-
|
55
|
+
result[key.to_s] = value
|
55
56
|
end
|
57
|
+
result
|
56
58
|
end
|
57
59
|
|
58
60
|
protected
|
59
61
|
|
60
|
-
def contains_all?(patterns)
|
61
|
-
rpt = sort_key.to_s
|
62
|
-
patterns.all? { |pattern| pattern =~ rpt }
|
63
|
-
end
|
64
|
-
|
65
62
|
def sort_key
|
66
63
|
[context, message, smell_category]
|
67
64
|
end
|
65
|
+
|
66
|
+
def common_parameters_equal?(other_parameters)
|
67
|
+
other_parameters.keys.each do |key|
|
68
|
+
unless parameters.key?(key)
|
69
|
+
raise ArgumentError, "The parameter #{key} you want to check for doesn't exist"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Why not check for strict parameter equality instead of just the common ones?
|
74
|
+
#
|
75
|
+
# In `self`, `parameters` might look like this: {:name=>"@other.thing", :count=>2}
|
76
|
+
# Coming from specs, 'other_parameters' might look like this, e.g.:
|
77
|
+
# {:name=>"@other.thing"}
|
78
|
+
# So in this spec we are just specifying the "name" parameter but not the "count".
|
79
|
+
# In order to allow for this kind of leniency we just test for common parameter equality,
|
80
|
+
# not for a strict one.
|
81
|
+
parameters.values_at(*other_parameters.keys) == other_parameters.values
|
82
|
+
end
|
68
83
|
end
|
69
84
|
end
|
@@ -69,7 +69,8 @@ module Reek
|
|
69
69
|
[SmellWarning.new(self,
|
70
70
|
context: method_ctx.full_name,
|
71
71
|
lines: [method_ctx.exp.line],
|
72
|
-
message: "doesn't depend on instance state"
|
72
|
+
message: "doesn't depend on instance state",
|
73
|
+
parameters: { name: method_ctx.full_name })]
|
73
74
|
end
|
74
75
|
|
75
76
|
private
|
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'reek/examiner'
|
2
|
-
require 'reek/cli/report/report'
|
3
2
|
require 'reek/cli/report/formatter'
|
4
|
-
require 'reek/cli/report/strategy'
|
5
3
|
|
6
4
|
module Reek
|
7
5
|
module Spec
|
@@ -23,7 +21,6 @@ module Reek
|
|
23
21
|
"Expected no smells, but got:\n#{rpt}"
|
24
22
|
end
|
25
23
|
end
|
26
|
-
|
27
24
|
#
|
28
25
|
# Returns +true+ if and only if the target source code contains smells.
|
29
26
|
#
|
@@ -6,34 +6,83 @@ module Reek
|
|
6
6
|
# An rspec matcher that matches when the +actual+ has the specified
|
7
7
|
# code smell.
|
8
8
|
#
|
9
|
-
class ShouldReekOf
|
10
|
-
def initialize(
|
11
|
-
@
|
12
|
-
@
|
9
|
+
class ShouldReekOf
|
10
|
+
def initialize(smell_category, smell_details = {})
|
11
|
+
@smell_category = normalize smell_category
|
12
|
+
@smell_details = smell_details
|
13
13
|
end
|
14
14
|
|
15
15
|
def matches?(actual)
|
16
16
|
@examiner = Examiner.new(actual)
|
17
17
|
@all_smells = @examiner.smells
|
18
|
-
@all_smells.any? { |warning| warning.matches?(@
|
18
|
+
@all_smells.any? { |warning| warning.matches?(@smell_category, @smell_details) }
|
19
19
|
end
|
20
20
|
|
21
21
|
def failure_message
|
22
|
-
"Expected #{@examiner.description} to reek of #{@
|
22
|
+
"Expected #{@examiner.description} to reek of #{@smell_category}, but it didn't"
|
23
23
|
end
|
24
24
|
|
25
25
|
def failure_message_when_negated
|
26
|
-
"Expected #{@examiner.description} not to reek of #{@
|
26
|
+
"Expected #{@examiner.description} not to reek of #{@smell_category}, but it did"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def normalize(smell_category_or_type)
|
32
|
+
# In theory, users can give us many different types of input (see the documentation for
|
33
|
+
# reek_of below), however we're basically ignoring all of those subleties and just
|
34
|
+
# return a string with the prepending namespace stripped.
|
35
|
+
smell_category_or_type.to_s.split(/::/)[-1]
|
27
36
|
end
|
28
37
|
end
|
29
38
|
|
30
39
|
#
|
31
|
-
# Checks the target source code for instances of
|
32
|
-
# and returns
|
33
|
-
#
|
40
|
+
# Checks the target source code for instances of "smell category"
|
41
|
+
# and returns true only if it can find one of them that matches.
|
42
|
+
#
|
43
|
+
# Remember that this includes our "smell types" as well. So it could be the
|
44
|
+
# "smell type" UtilityFunction, which is represented as a concrete class
|
45
|
+
# in reek but it could also be "Duplication" which is a "smell categgory".
|
46
|
+
#
|
47
|
+
# In theory you could pass many different types of input here:
|
48
|
+
# - :UtilityFunction
|
49
|
+
# - "UtilityFunction"
|
50
|
+
# - UtilityFunction (this works in our specs because we tend to do "include Reek:Smells")
|
51
|
+
# - Reek::Smells::UtilityFunction (the right way if you really want to pass a class)
|
52
|
+
# - "Duplication" or :Duplication which is an abstract "smell category"
|
53
|
+
#
|
54
|
+
# It is recommended to pass this as a symbol like :UtilityFunction. However we don't
|
55
|
+
# enforce this.
|
56
|
+
#
|
57
|
+
# Additionally you can be more specific and pass in "smell_details" you
|
58
|
+
# want to check for as well e.g. "name" or "count" (see the examples below).
|
59
|
+
# The parameters you can check for are depending on the smell you are checking for.
|
60
|
+
# For instance "count" doesn't make sense everywhere whereas "name" does in most cases.
|
61
|
+
# If you pass in a parameter that doesn't exist (e.g. you make a typo like "namme") reek will
|
62
|
+
# raise an ArgumentError to give you a hint that you passed something that doesn't make
|
63
|
+
# much sense.
|
64
|
+
#
|
65
|
+
# smell_category - The "smell category" or "smell_type" we check for.
|
66
|
+
# smells_details - A hash containing "smell warning" parameters
|
67
|
+
#
|
68
|
+
# Examples
|
69
|
+
#
|
70
|
+
# Without smell_details:
|
71
|
+
#
|
72
|
+
# reek_of(:FeatureEnvy)
|
73
|
+
# reek_of(Reek::Smells::UtilityFunction)
|
74
|
+
#
|
75
|
+
# With smell_details:
|
76
|
+
#
|
77
|
+
# reek_of(UncommunicativeParameterName, name: 'x2')
|
78
|
+
# reek_of(DataClump, count: 3)
|
79
|
+
#
|
80
|
+
# Examples from a real spec
|
81
|
+
#
|
82
|
+
# expect(src).to reek_of(Reek::Smells::DuplicateMethodCall, name: '@other.thing')
|
34
83
|
#
|
35
|
-
def reek_of(smell_category,
|
36
|
-
ShouldReekOf.new(smell_category,
|
84
|
+
def reek_of(smell_category, smell_details = {})
|
85
|
+
ShouldReekOf.new(smell_category, smell_details)
|
37
86
|
end
|
38
87
|
end
|
39
88
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'reek/examiner'
|
2
|
-
require 'reek/cli/report/report'
|
3
2
|
require 'reek/cli/report/formatter'
|
4
|
-
require 'reek/cli/report/strategy'
|
5
3
|
|
6
4
|
module Reek
|
7
5
|
module Spec
|
@@ -9,7 +7,7 @@ module Reek
|
|
9
7
|
# An rspec matcher that matches when the +actual+ has the specified
|
10
8
|
# code smell and no others.
|
11
9
|
#
|
12
|
-
class ShouldReekOnlyOf < ShouldReekOf
|
10
|
+
class ShouldReekOnlyOf < ShouldReekOf
|
13
11
|
def matches?(actual)
|
14
12
|
matches_examiner?(Examiner.new(actual))
|
15
13
|
end
|
@@ -17,25 +15,29 @@ module Reek
|
|
17
15
|
def matches_examiner?(examiner)
|
18
16
|
@examiner = examiner
|
19
17
|
@warnings = @examiner.smells
|
20
|
-
|
18
|
+
return false if @warnings.empty?
|
19
|
+
@warnings.all? { |warning| warning.matches?(@smell_category) }
|
21
20
|
end
|
22
21
|
|
23
22
|
def failure_message
|
24
23
|
rpt = Cli::Report::Formatter.format_list(@warnings)
|
25
|
-
"Expected #{@examiner.description} to reek only of #{@
|
24
|
+
"Expected #{@examiner.description} to reek only of #{@smell_category}, but got:\n#{rpt}"
|
26
25
|
end
|
27
26
|
|
28
27
|
def failure_message_when_negated
|
29
|
-
"Expected #{@examiner.description} not to reek only of #{@
|
28
|
+
"Expected #{@examiner.description} not to reek only of #{@smell_category}, but it did"
|
30
29
|
end
|
31
30
|
end
|
32
31
|
|
33
32
|
#
|
34
|
-
#
|
35
|
-
# any kind in the target source code's Reek report.
|
33
|
+
# See the documentaton for "reek_of".
|
36
34
|
#
|
37
|
-
|
38
|
-
|
35
|
+
# Notable differences to reek_of:
|
36
|
+
# 1.) "reek_of" doesn't mind if there are other smells of a different category.
|
37
|
+
# "reek_only_of" will fail in that case.
|
38
|
+
# 2.) "reek_only_of" doesn't support the additional smell_details hash.
|
39
|
+
def reek_only_of(smell_category)
|
40
|
+
ShouldReekOnlyOf.new(smell_category)
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
data/lib/reek/version.rb
CHANGED
data/reek.gemspec
CHANGED
@@ -30,9 +30,9 @@ Gem::Specification.new do |s|
|
|
30
30
|
s.required_ruby_version = '>= 1.9.3'
|
31
31
|
s.summary = 'Code smell detector for Ruby'
|
32
32
|
|
33
|
-
s.add_runtime_dependency('parser', ['~> 2.2
|
33
|
+
s.add_runtime_dependency('parser', ['~> 2.2'])
|
34
34
|
s.add_runtime_dependency('unparser', ['~> 0.2.2'])
|
35
|
-
s.add_runtime_dependency('rainbow', ['
|
35
|
+
s.add_runtime_dependency('rainbow', ['~> 2.0'])
|
36
36
|
|
37
37
|
s.add_development_dependency('bundler', ['~> 1.1'])
|
38
38
|
s.add_development_dependency('rake', ['~> 10.0'])
|
data/spec/factories/factories.rb
CHANGED
@@ -7,10 +7,7 @@ FactoryGirl.define do
|
|
7
7
|
source 'dummy_file'
|
8
8
|
|
9
9
|
initialize_with do
|
10
|
-
|
11
|
-
Kernel.const_get('Reek').
|
12
|
-
const_get('Smells').
|
13
|
-
const_get(smell_type).new(source)
|
10
|
+
::Reek::Smells.const_get(smell_type).new(source)
|
14
11
|
end
|
15
12
|
end
|
16
13
|
|
@@ -20,7 +17,7 @@ FactoryGirl.define do
|
|
20
17
|
context 'self'
|
21
18
|
lines [42]
|
22
19
|
message 'smell warning message'
|
23
|
-
parameters
|
20
|
+
parameters { {} }
|
24
21
|
|
25
22
|
initialize_with do
|
26
23
|
new(smell_detector, context: context,
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'reek/examiner'
|
3
|
+
require 'reek/cli/report/report'
|
4
|
+
|
5
|
+
include Reek
|
6
|
+
include Reek::Cli
|
7
|
+
|
8
|
+
describe Report::HtmlReport do
|
9
|
+
let(:instance) { Report::HtmlReport.new }
|
10
|
+
|
11
|
+
context 'with an empty source' do
|
12
|
+
let(:examiner) { Examiner.new('') }
|
13
|
+
|
14
|
+
before do
|
15
|
+
instance.add_examiner examiner
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has the text 0 total warnings' do
|
19
|
+
instance.show
|
20
|
+
|
21
|
+
file = File.expand_path('../../../../reek.html', __FILE__)
|
22
|
+
text = File.read(file)
|
23
|
+
File.delete(file)
|
24
|
+
|
25
|
+
expect(text).to include('0 total warnings')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'reek/cli/options'
|
4
|
+
|
5
|
+
describe Reek::Cli::OptionInterpreter do
|
6
|
+
let(:options) { OpenStruct.new }
|
7
|
+
let(:instance) { Reek::Cli::OptionInterpreter.new(options) }
|
8
|
+
|
9
|
+
describe '#reporter' do
|
10
|
+
it 'returns a Report::TextReport instance by default' do
|
11
|
+
expect(instance.reporter).to be_instance_of Reek::Cli::Report::TextReport
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'reek/examiner'
|
3
|
+
require 'reek/cli/report/report'
|
4
|
+
require 'reek/cli/report/formatter'
|
5
|
+
require 'reek/cli/report/heading_formatter'
|
6
|
+
require 'rainbow'
|
7
|
+
|
8
|
+
include Reek
|
9
|
+
include Reek::Cli
|
10
|
+
|
11
|
+
describe Report::TextReport do
|
12
|
+
let(:report_options) do
|
13
|
+
{
|
14
|
+
warning_formatter: Report::SimpleWarningFormatter.new,
|
15
|
+
report_formatter: Report::Formatter,
|
16
|
+
heading_formatter: Report::HeadingFormatter::Quiet
|
17
|
+
}
|
18
|
+
end
|
19
|
+
let(:instance) { Report::TextReport.new report_options }
|
20
|
+
|
21
|
+
context 'with a single empty source' do
|
22
|
+
before do
|
23
|
+
instance.add_examiner Examiner.new('')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has an empty quiet_report' do
|
27
|
+
expect { instance.show }.to_not output.to_stdout
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'with non smelly files' do
|
32
|
+
before do
|
33
|
+
instance.add_examiner(Examiner.new('def simple() puts "a" end'))
|
34
|
+
instance.add_examiner(Examiner.new('def simple() puts "a" end'))
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with colors disabled' do
|
38
|
+
before :each do
|
39
|
+
Rainbow.enabled = false
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'shows total of 0 warnings' do
|
43
|
+
expect { instance.show }.to output(/0 total warnings\n\Z/).to_stdout
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with colors enabled' do
|
48
|
+
before :each do
|
49
|
+
Rainbow.enabled = true
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'has a footer in color' do
|
53
|
+
expect { instance.show }.to output(/\e\[32m0 total warnings\n\e\[0m\Z/).to_stdout
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with a couple of smells' do
|
59
|
+
before do
|
60
|
+
instance.add_examiner(Examiner.new('def simple(a) a[3] end'))
|
61
|
+
instance.add_examiner(Examiner.new('def simple(a) a[3] end'))
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'with colors disabled' do
|
65
|
+
before do
|
66
|
+
Rainbow.enabled = false
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'has a heading' do
|
70
|
+
expect { instance.show }.to output(/string -- 2 warnings/).to_stdout
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should mention every smell name' do
|
74
|
+
expect { instance.show }.to output(/UncommunicativeParameterName/).to_stdout
|
75
|
+
expect { instance.show }.to output(/FeatureEnvy/).to_stdout
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'with colors enabled' do
|
80
|
+
before do
|
81
|
+
Rainbow.enabled = true
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'has a header in color' do
|
85
|
+
expect { instance.show }.
|
86
|
+
to output(/\A\e\[36mstring -- \e\[0m\e\[33m2 warning\e\[0m\e\[33ms\e\[0m/).to_stdout
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'has a footer in color' do
|
90
|
+
expect { instance.show }.
|
91
|
+
to output(/\e\[31m4 total warnings\n\e\[0m\Z/).to_stdout
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|