reek 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|