reek 1.6.6 → 2.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.
- 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
|