reek 2.0.0 → 2.0.1
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 +5 -0
- data/README.md +13 -2
- data/Rakefile +2 -1
- data/features/command_line_interface/options.feature +2 -3
- data/features/command_line_interface/smell_selection.feature +0 -1
- data/features/command_line_interface/smells_count.feature +0 -2
- data/features/command_line_interface/stdin.feature +3 -10
- data/features/configuration_files/masking_smells.feature +1 -5
- data/features/configuration_files/overrides_defaults.feature +0 -1
- data/features/rake_task/rake_task.feature +1 -4
- data/features/reports/json.feature +73 -0
- data/features/reports/reports.feature +0 -3
- data/features/reports/yaml.feature +0 -1
- data/features/ruby_api/api.feature +0 -1
- data/features/samples.feature +0 -4
- data/features/step_definitions/reek_steps.rb +14 -4
- data/features/support/env.rb +3 -0
- data/lib/reek/cli/option_interpreter.rb +2 -0
- data/lib/reek/cli/options.rb +3 -3
- data/lib/reek/cli/report/formatter.rb +1 -1
- data/lib/reek/cli/report/location_formatter.rb +11 -0
- data/lib/reek/cli/report/report.rb +11 -2
- data/lib/reek/core/code_context.rb +49 -10
- data/lib/reek/core/sniffer.rb +2 -2
- data/lib/reek/core/{code_parser.rb → tree_walker.rb} +16 -7
- data/lib/reek/examiner.rb +0 -39
- data/lib/reek/smells.rb +3 -25
- data/lib/reek/smells/feature_envy.rb +0 -2
- data/lib/reek/smells/smell_detector.rb +0 -6
- data/lib/reek/source/sexp_extensions.rb +17 -6
- data/lib/reek/source/sexp_node.rb +1 -1
- data/lib/reek/spec.rb +66 -0
- data/lib/reek/spec/should_reek.rb +0 -6
- data/lib/reek/spec/should_reek_of.rb +0 -49
- data/lib/reek/spec/should_reek_only_of.rb +0 -11
- data/lib/reek/version.rb +6 -1
- data/reek.gemspec +3 -2
- data/spec/reek/cli/json_report_spec.rb +20 -0
- data/spec/reek/core/code_context_spec.rb +6 -0
- data/spec/reek/core/smell_configuration_spec.rb +3 -3
- data/spec/reek/core/{code_parser_spec.rb → tree_walker_spec.rb} +4 -4
- data/spec/reek/examiner_spec.rb +0 -19
- data/spec/reek/smells/attribute_spec.rb +6 -9
- data/spec/reek/smells/boolean_parameter_spec.rb +13 -15
- data/spec/reek/smells/class_variable_spec.rb +17 -17
- data/spec/reek/smells/control_parameter_spec.rb +44 -46
- data/spec/reek/smells/data_clump_spec.rb +73 -70
- data/spec/reek/smells/duplicate_method_call_spec.rb +39 -37
- data/spec/reek/smells/feature_envy_spec.rb +53 -57
- data/spec/reek/smells/irresponsible_module_spec.rb +12 -11
- data/spec/reek/smells/long_parameter_list_spec.rb +33 -26
- data/spec/reek/smells/long_yield_list_spec.rb +15 -19
- data/spec/reek/smells/module_initialize_spec.rb +4 -6
- data/spec/reek/smells/nested_iterators_spec.rb +28 -28
- data/spec/reek/smells/nil_check_spec.rb +18 -20
- data/spec/reek/smells/prima_donna_method_spec.rb +6 -9
- data/spec/reek/smells/repeated_conditional_spec.rb +41 -43
- data/spec/reek/smells/too_many_instance_variables_spec.rb +6 -11
- data/spec/reek/smells/too_many_methods_spec.rb +44 -61
- data/spec/reek/smells/too_many_statements_spec.rb +14 -41
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +6 -11
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +10 -14
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +15 -16
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +33 -36
- data/spec/reek/smells/unused_parameters_spec.rb +16 -19
- data/spec/reek/smells/utility_function_spec.rb +7 -10
- metadata +22 -6
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rainbow'
|
2
|
+
require 'json'
|
2
3
|
|
3
4
|
module Reek
|
4
5
|
module Cli
|
@@ -58,12 +59,11 @@ module Reek
|
|
58
59
|
private
|
59
60
|
|
60
61
|
def display_summary
|
61
|
-
|
62
|
+
smells.reject(&:empty?).each { |smell| puts smell }
|
62
63
|
end
|
63
64
|
|
64
65
|
def display_total_smell_count
|
65
66
|
return unless @examiners.size > 1
|
66
|
-
print "\n"
|
67
67
|
print total_smell_count_message
|
68
68
|
end
|
69
69
|
|
@@ -102,6 +102,15 @@ module Reek
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
+
#
|
106
|
+
# Displays a list of smells in JSON format
|
107
|
+
# JSON with empty array for 0 smells
|
108
|
+
class JsonReport < Base
|
109
|
+
def show
|
110
|
+
print ::JSON.generate(smells.map(&:yaml_hash))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
105
114
|
#
|
106
115
|
# Saves the report as a HTML file
|
107
116
|
#
|
@@ -10,9 +10,45 @@ module Reek
|
|
10
10
|
class CodeContext
|
11
11
|
attr_reader :exp
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
# Initializes a new CodeContext.
|
14
|
+
#
|
15
|
+
# context - *_context from the `core` namespace
|
16
|
+
# exp - Reek::Source::AstNode
|
17
|
+
#
|
18
|
+
# Examples:
|
19
|
+
#
|
20
|
+
# Given something like:
|
21
|
+
#
|
22
|
+
# class Omg; def foo(x); puts x; end; end
|
23
|
+
#
|
24
|
+
# the first time this is instantianted from TreeWalker `context` is a StopContext:
|
25
|
+
#
|
26
|
+
# #<Reek::Core::StopContext:0x00000002231098 @name="">
|
27
|
+
#
|
28
|
+
# and `exp` looks like this:
|
29
|
+
#
|
30
|
+
# (class
|
31
|
+
# (const nil :Omg) nil
|
32
|
+
# (def :foo
|
33
|
+
# (args
|
34
|
+
# (arg :x))
|
35
|
+
# (send nil :puts
|
36
|
+
# (lvar :x))))
|
37
|
+
#
|
38
|
+
# The next time we instantiate a CodeContext via TreeWalker `context` would be:
|
39
|
+
#
|
40
|
+
# Reek::Core::ModuleContext
|
41
|
+
#
|
42
|
+
# and `exp` is:
|
43
|
+
#
|
44
|
+
# (def :foo
|
45
|
+
# (args
|
46
|
+
# (arg :x))
|
47
|
+
# (send nil :puts
|
48
|
+
# (lvar :x)))
|
49
|
+
def initialize(context, exp)
|
50
|
+
@context = context
|
51
|
+
@exp = exp
|
16
52
|
end
|
17
53
|
|
18
54
|
def name
|
@@ -29,7 +65,10 @@ module Reek
|
|
29
65
|
|
30
66
|
def matches?(candidates)
|
31
67
|
my_fq_name = full_name
|
32
|
-
candidates.any?
|
68
|
+
candidates.any? do |candidate|
|
69
|
+
candidate = Regexp.quote(candidate) if candidate.is_a?(String)
|
70
|
+
/#{candidate}/ =~ my_fq_name
|
71
|
+
end
|
33
72
|
end
|
34
73
|
|
35
74
|
#
|
@@ -37,7 +76,7 @@ module Reek
|
|
37
76
|
# that knows how to deal with the request.
|
38
77
|
#
|
39
78
|
def method_missing(method, *args)
|
40
|
-
@
|
79
|
+
@context.send(method, *args)
|
41
80
|
end
|
42
81
|
|
43
82
|
def num_methods
|
@@ -45,12 +84,12 @@ module Reek
|
|
45
84
|
end
|
46
85
|
|
47
86
|
def full_name
|
48
|
-
|
49
|
-
exp.full_name(
|
87
|
+
context = @context ? @context.full_name : ''
|
88
|
+
exp.full_name(context)
|
50
89
|
end
|
51
90
|
|
52
91
|
def config_for(detector_class)
|
53
|
-
|
92
|
+
context_config_for(detector_class).merge(
|
54
93
|
config[detector_class.smell_type] || {})
|
55
94
|
end
|
56
95
|
|
@@ -64,8 +103,8 @@ module Reek
|
|
64
103
|
end
|
65
104
|
end
|
66
105
|
|
67
|
-
def
|
68
|
-
@
|
106
|
+
def context_config_for(detector_class)
|
107
|
+
@context ? @context.config_for(detector_class) : {}
|
69
108
|
end
|
70
109
|
end
|
71
110
|
end
|
data/lib/reek/core/sniffer.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'reek/core/
|
1
|
+
require 'reek/core/tree_walker'
|
2
2
|
require 'reek/core/smell_repository'
|
3
3
|
require 'reek/configuration/app_configuration'
|
4
4
|
|
@@ -17,7 +17,7 @@ module Reek
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def report_on(listener)
|
20
|
-
|
20
|
+
TreeWalker.new(@smell_repository).process(syntax_tree) if syntax_tree
|
21
21
|
@smell_repository.report_on(listener)
|
22
22
|
end
|
23
23
|
|
@@ -12,21 +12,26 @@ module Reek
|
|
12
12
|
# SMELL: This class is responsible for counting statements and for feeding
|
13
13
|
# each context to the smell repository.
|
14
14
|
# SMELL: This class has a name that doesn't match its responsibility.
|
15
|
-
class
|
16
|
-
def initialize(smell_repository
|
15
|
+
class TreeWalker
|
16
|
+
def initialize(smell_repository)
|
17
17
|
@smell_repository = smell_repository
|
18
|
-
@element =
|
18
|
+
@element = StopContext.new
|
19
19
|
end
|
20
20
|
|
21
21
|
def process(exp)
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
context_processor = "process_#{exp.type}"
|
23
|
+
if context_processor_exists?(context_processor)
|
24
|
+
send(context_processor, exp)
|
25
|
+
else
|
26
|
+
process_default exp
|
27
|
+
end
|
25
28
|
@element
|
26
29
|
end
|
27
30
|
|
28
31
|
def process_default(exp)
|
29
|
-
exp.children.each
|
32
|
+
exp.children.each do |child|
|
33
|
+
process(child) if child.is_a? AST::Node
|
34
|
+
end
|
30
35
|
end
|
31
36
|
|
32
37
|
def process_module(exp)
|
@@ -140,6 +145,10 @@ module Reek
|
|
140
145
|
|
141
146
|
private
|
142
147
|
|
148
|
+
def context_processor_exists?(name)
|
149
|
+
respond_to?(name)
|
150
|
+
end
|
151
|
+
|
143
152
|
def count_clause(sexp)
|
144
153
|
@element.count_statements(1) if sexp
|
145
154
|
end
|
data/lib/reek/examiner.rb
CHANGED
@@ -16,9 +16,6 @@ module Reek
|
|
16
16
|
#
|
17
17
|
# Creates an Examiner which scans the given +source+ for code smells.
|
18
18
|
#
|
19
|
-
# The smells reported against any source file can be "masked" by
|
20
|
-
# creating *.reek files. See TBS for details.
|
21
|
-
#
|
22
19
|
# @param [Source::SourceCode, Array<String>, #to_reek_source]
|
23
20
|
# If +source+ is a String it is assumed to be Ruby source code;
|
24
21
|
# if it is a File, the file is opened and parsed for Ruby source code;
|
@@ -64,41 +61,5 @@ module Reek
|
|
64
61
|
def smelly?
|
65
62
|
!smells.empty?
|
66
63
|
end
|
67
|
-
|
68
|
-
#
|
69
|
-
# Returns an Array of SmellWarning objects, one for each non-masked smell
|
70
|
-
# in the source.
|
71
|
-
#
|
72
|
-
# @deprecated Use #smells instead.
|
73
|
-
#
|
74
|
-
alias_method :all_active_smells, :smells
|
75
|
-
|
76
|
-
#
|
77
|
-
# Returns an Array of SmellWarning objects, one for each smell
|
78
|
-
# in the source; includes active smells and masked smells.
|
79
|
-
#
|
80
|
-
# @return [Array<SmellWarning>]
|
81
|
-
#
|
82
|
-
# @deprecated Use #smells instead.
|
83
|
-
#
|
84
|
-
alias_method :all_smells, :smells
|
85
|
-
|
86
|
-
#
|
87
|
-
# Returns the number of non-masked smells in the source.
|
88
|
-
#
|
89
|
-
# @deprecated Use #smells_count instead.
|
90
|
-
#
|
91
|
-
def num_active_smells
|
92
|
-
smells.length
|
93
|
-
end
|
94
|
-
|
95
|
-
#
|
96
|
-
# Returns the number of masked smells in the source.
|
97
|
-
#
|
98
|
-
# @deprecated Masked smells are no longer reported; this method always returns 0.
|
99
|
-
#
|
100
|
-
def num_masked_smells
|
101
|
-
0
|
102
|
-
end
|
103
64
|
end
|
104
65
|
end
|
data/lib/reek/smells.rb
CHANGED
@@ -1,28 +1,6 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
require 'reek/smells/control_parameter'
|
5
|
-
require 'reek/smells/data_clump'
|
6
|
-
require 'reek/smells/duplicate_method_call'
|
7
|
-
require 'reek/smells/feature_envy'
|
8
|
-
require 'reek/smells/irresponsible_module'
|
9
|
-
require 'reek/smells/long_parameter_list'
|
10
|
-
require 'reek/smells/long_yield_list'
|
11
|
-
require 'reek/smells/module_initialize'
|
12
|
-
require 'reek/smells/nested_iterators'
|
13
|
-
require 'reek/smells/nil_check'
|
14
|
-
require 'reek/smells/prima_donna_method'
|
15
|
-
require 'reek/smells/repeated_conditional'
|
16
|
-
require 'reek/smells/too_many_instance_variables'
|
17
|
-
require 'reek/smells/too_many_methods'
|
18
|
-
require 'reek/smells/too_many_statements'
|
19
|
-
require 'reek/smells/uncommunicative_method_name'
|
20
|
-
require 'reek/smells/uncommunicative_module_name'
|
21
|
-
require 'reek/smells/uncommunicative_parameter_name'
|
22
|
-
require 'reek/smells/uncommunicative_variable_name'
|
23
|
-
require 'reek/smells/unused_parameters'
|
24
|
-
require 'reek/smells/utility_function'
|
25
|
-
# SMELL: Duplication -- all these should be found automagically
|
1
|
+
require 'require_all'
|
2
|
+
|
3
|
+
require_rel 'smells'
|
26
4
|
|
27
5
|
module Reek
|
28
6
|
#
|
@@ -2,6 +2,10 @@ require 'reek/source/sexp_node'
|
|
2
2
|
|
3
3
|
module Reek
|
4
4
|
module Source
|
5
|
+
#
|
6
|
+
# Extension modules providing utility methods to AstNode objects, depending
|
7
|
+
# on their type.
|
8
|
+
#
|
5
9
|
module SexpExtensions
|
6
10
|
# Base module for utility methods for argument nodes.
|
7
11
|
module ArgNodeBase
|
@@ -96,21 +100,22 @@ module Reek
|
|
96
100
|
end
|
97
101
|
end
|
98
102
|
|
103
|
+
# Utility methods for :and nodes.
|
99
104
|
module AndNode
|
100
105
|
include LogicOperatorBase
|
101
106
|
end
|
102
107
|
|
103
|
-
# Utility methods for :or nodes
|
108
|
+
# Utility methods for :or nodes.
|
104
109
|
module OrNode
|
105
110
|
include LogicOperatorBase
|
106
111
|
end
|
107
112
|
|
108
|
-
# Utility methods for :attrasgn nodes
|
113
|
+
# Utility methods for :attrasgn nodes.
|
109
114
|
module AttrasgnNode
|
110
115
|
def args() self[3] end
|
111
116
|
end
|
112
117
|
|
113
|
-
# Utility methods for :case nodes
|
118
|
+
# Utility methods for :case nodes.
|
114
119
|
module CaseNode
|
115
120
|
def condition() self[1] end
|
116
121
|
|
@@ -123,7 +128,7 @@ module Reek
|
|
123
128
|
end
|
124
129
|
end
|
125
130
|
|
126
|
-
# Utility methods for :when nodes
|
131
|
+
# Utility methods for :when nodes.
|
127
132
|
module WhenNode
|
128
133
|
def condition_list
|
129
134
|
children[0..-2]
|
@@ -134,7 +139,7 @@ module Reek
|
|
134
139
|
end
|
135
140
|
end
|
136
141
|
|
137
|
-
# Utility methods for :send nodes
|
142
|
+
# Utility methods for :send nodes.
|
138
143
|
module SendNode
|
139
144
|
def receiver() self[1] end
|
140
145
|
def method_name() self[2] end
|
@@ -172,6 +177,7 @@ module Reek
|
|
172
177
|
include VariableBase
|
173
178
|
end
|
174
179
|
|
180
|
+
# Utility methods for :lvar nodes.
|
175
181
|
module LvarNode
|
176
182
|
def var_name() self[1] end
|
177
183
|
end
|
@@ -226,6 +232,7 @@ module Reek
|
|
226
232
|
end
|
227
233
|
end
|
228
234
|
|
235
|
+
# Utility methods for :defs nodes.
|
229
236
|
module DefsNode
|
230
237
|
def receiver() self[1] end
|
231
238
|
def name() self[2] end
|
@@ -242,7 +249,7 @@ module Reek
|
|
242
249
|
end
|
243
250
|
end
|
244
251
|
|
245
|
-
# Utility methods for :if nodes
|
252
|
+
# Utility methods for :if nodes.
|
246
253
|
module IfNode
|
247
254
|
def condition() self[1] end
|
248
255
|
|
@@ -263,6 +270,7 @@ module Reek
|
|
263
270
|
end
|
264
271
|
end
|
265
272
|
|
273
|
+
# Utility methods for :lit nodes.
|
266
274
|
module LitNode
|
267
275
|
def value() self[1] end
|
268
276
|
end
|
@@ -292,11 +300,13 @@ module Reek
|
|
292
300
|
end
|
293
301
|
end
|
294
302
|
|
303
|
+
# Utility methods for :class nodes.
|
295
304
|
module ClassNode
|
296
305
|
include ModuleNode
|
297
306
|
def superclass() self[2] end
|
298
307
|
end
|
299
308
|
|
309
|
+
# Utility methods for :yield nodes.
|
300
310
|
module YieldNode
|
301
311
|
def args() self[1..-1] end
|
302
312
|
|
@@ -305,6 +315,7 @@ module Reek
|
|
305
315
|
end
|
306
316
|
end
|
307
317
|
|
318
|
+
# Utility methods for :zsuper nodes.
|
308
319
|
module ZsuperNode
|
309
320
|
def method_name
|
310
321
|
:super
|
data/lib/reek/spec.rb
CHANGED
@@ -40,6 +40,72 @@ module Reek
|
|
40
40
|
# ruby.should_not reek_of(:FeatureEnvy)
|
41
41
|
#
|
42
42
|
module Spec
|
43
|
+
#
|
44
|
+
# Checks the target source code for instances of "smell category"
|
45
|
+
# and returns true only if it can find one of them that matches.
|
46
|
+
#
|
47
|
+
# Remember that this includes our "smell types" as well. So it could be the
|
48
|
+
# "smell type" UtilityFunction, which is represented as a concrete class
|
49
|
+
# in reek but it could also be "Duplication" which is a "smell categgory".
|
50
|
+
#
|
51
|
+
# In theory you could pass many different types of input here:
|
52
|
+
# - :UtilityFunction
|
53
|
+
# - "UtilityFunction"
|
54
|
+
# - UtilityFunction (this works in our specs because we tend to do "include Reek:Smells")
|
55
|
+
# - Reek::Smells::UtilityFunction (the right way if you really want to pass a class)
|
56
|
+
# - "Duplication" or :Duplication which is an abstract "smell category"
|
57
|
+
#
|
58
|
+
# It is recommended to pass this as a symbol like :UtilityFunction. However we don't
|
59
|
+
# enforce this.
|
60
|
+
#
|
61
|
+
# Additionally you can be more specific and pass in "smell_details" you
|
62
|
+
# want to check for as well e.g. "name" or "count" (see the examples below).
|
63
|
+
# The parameters you can check for are depending on the smell you are checking for.
|
64
|
+
# For instance "count" doesn't make sense everywhere whereas "name" does in most cases.
|
65
|
+
# If you pass in a parameter that doesn't exist (e.g. you make a typo like "namme") reek will
|
66
|
+
# raise an ArgumentError to give you a hint that you passed something that doesn't make
|
67
|
+
# much sense.
|
68
|
+
#
|
69
|
+
# smell_category - The "smell category" or "smell_type" we check for.
|
70
|
+
# smells_details - A hash containing "smell warning" parameters
|
71
|
+
#
|
72
|
+
# Examples
|
73
|
+
#
|
74
|
+
# Without smell_details:
|
75
|
+
#
|
76
|
+
# reek_of(:FeatureEnvy)
|
77
|
+
# reek_of(Reek::Smells::UtilityFunction)
|
78
|
+
#
|
79
|
+
# With smell_details:
|
80
|
+
#
|
81
|
+
# reek_of(UncommunicativeParameterName, name: 'x2')
|
82
|
+
# reek_of(DataClump, count: 3)
|
83
|
+
#
|
84
|
+
# Examples from a real spec
|
85
|
+
#
|
86
|
+
# expect(src).to reek_of(Reek::Smells::DuplicateMethodCall, name: '@other.thing')
|
87
|
+
#
|
88
|
+
def reek_of(smell_category, smell_details = {})
|
89
|
+
ShouldReekOf.new(smell_category, smell_details)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# See the documentaton for "reek_of".
|
94
|
+
#
|
95
|
+
# Notable differences to reek_of:
|
96
|
+
# 1.) "reek_of" doesn't mind if there are other smells of a different category.
|
97
|
+
# "reek_only_of" will fail in that case.
|
98
|
+
# 2.) "reek_only_of" doesn't support the additional smell_details hash.
|
99
|
+
def reek_only_of(smell_category)
|
100
|
+
ShouldReekOnlyOf.new(smell_category)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Returns +true+ if and only if the target source code contains smells.
|
105
|
+
#
|
106
|
+
def reek
|
107
|
+
ShouldReek.new
|
108
|
+
end
|
43
109
|
end
|
44
110
|
end
|
45
111
|
|