reek 1.3.4 → 1.3.5
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 +27 -2
- data/features/command_line_interface/options.feature +5 -2
- data/features/command_line_interface/smells_count.feature +43 -45
- data/features/command_line_interface/stdin.feature +9 -15
- data/features/configuration_files/masking_smells.feature +9 -17
- data/features/rake_task/rake_task.feature +4 -4
- data/features/reports/reports.feature +80 -21
- data/features/samples.feature +8 -18
- data/features/step_definitions/reek_steps.rb +4 -0
- data/lib/reek/cli/application.rb +3 -6
- data/lib/reek/cli/command_line.rb +16 -6
- data/lib/reek/cli/reek_command.rb +4 -12
- data/lib/reek/cli/report.rb +61 -19
- data/lib/reek/config_file_exception.rb +5 -0
- data/lib/reek/smells/control_parameter.rb +45 -14
- data/lib/reek/smells/data_clump.rb +15 -39
- data/lib/reek/smells/duplicate_method_call.rb +76 -26
- data/lib/reek/source/config_file.rb +30 -19
- data/lib/reek/source/sexp_extensions.rb +139 -0
- data/lib/reek/source/sexp_node.rb +64 -0
- data/lib/reek/source/source_code.rb +1 -1
- data/lib/reek/source/tree_dresser.rb +30 -175
- data/lib/reek/spec/should_reek.rb +2 -5
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +1 -1
- data/spec/matchers/smell_of_matcher.rb +12 -15
- data/spec/reek/cli/report_spec.rb +10 -6
- data/spec/reek/core/code_parser_spec.rb +0 -6
- data/spec/reek/smells/control_parameter_spec.rb +195 -8
- data/spec/reek/smells/data_clump_spec.rb +28 -3
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +7 -7
- data/spec/reek/source/sexp_extensions_spec.rb +290 -0
- data/spec/reek/source/sexp_node_spec.rb +28 -0
- data/spec/reek/source/source_code_spec.rb +59 -19
- data/spec/reek/source/tree_dresser_spec.rb +7 -314
- data/spec/reek/spec/should_reek_spec.rb +51 -64
- data/spec/samples/all_but_one_masked/dirty.rb +2 -2
- data/spec/samples/corrupt_config_file/dirty.rb +1 -0
- data/spec/samples/masked/dirty.rb +1 -1
- data/spec/samples/masked_by_dotfile/dirty.rb +2 -2
- data/spec/samples/no_config_file/dirty.rb +8 -0
- data/spec/samples/not_quite_masked/dirty.rb +0 -3
- data/spec/samples/three_smelly_files/dirty_one.rb +3 -0
- data/spec/samples/three_smelly_files/dirty_three.rb +5 -0
- data/spec/samples/three_smelly_files/dirty_two.rb +4 -0
- data/spec/spec_helper.rb +5 -0
- metadata +145 -137
- data/spec/reek/cli/reek_command_spec.rb +0 -46
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'reek/config_file_exception'
|
2
3
|
|
3
4
|
module Reek
|
4
5
|
module Source
|
@@ -43,26 +44,25 @@ module Reek
|
|
43
44
|
end
|
44
45
|
|
45
46
|
#
|
46
|
-
# Load the file path with which this was initialized
|
47
|
-
#
|
48
|
-
#
|
47
|
+
# Load the file path with which this was initialized.
|
48
|
+
# Empty files are ignored with a warning. All other errors are to be
|
49
|
+
# handled farther up the stack.
|
49
50
|
#
|
50
51
|
def load
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
if Hash === result
|
55
|
-
return result
|
56
|
-
else
|
57
|
-
@@bad_config_files << @file_path # poop
|
58
|
-
problem('Not a hash')
|
59
|
-
end
|
60
|
-
rescue Exception => err
|
61
|
-
@@bad_config_files << @file_path # poop
|
62
|
-
problem(err.to_s)
|
63
|
-
end
|
52
|
+
if File.size(@file_path) == 0
|
53
|
+
problem('Empty file')
|
54
|
+
return {}
|
64
55
|
end
|
65
|
-
|
56
|
+
|
57
|
+
begin
|
58
|
+
result = YAML.load_file(@file_path) || {}
|
59
|
+
rescue => e
|
60
|
+
error(e.to_s)
|
61
|
+
end
|
62
|
+
|
63
|
+
error('Not a hash') unless Hash === result
|
64
|
+
|
65
|
+
result
|
66
66
|
end
|
67
67
|
|
68
68
|
#
|
@@ -70,8 +70,19 @@ module Reek
|
|
70
70
|
# Error.
|
71
71
|
#
|
72
72
|
def problem(reason)
|
73
|
-
$stderr.puts "
|
74
|
-
|
73
|
+
$stderr.puts "Warning: #{message(reason)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Report invalid configuration file to standard
|
78
|
+
# Error.
|
79
|
+
#
|
80
|
+
def error(reason)
|
81
|
+
raise ConfigFileException.new message(reason)
|
82
|
+
end
|
83
|
+
|
84
|
+
def message(reason)
|
85
|
+
"Invalid configuration file \"#{File.basename(@file_path)}\" -- #{reason}"
|
75
86
|
end
|
76
87
|
end
|
77
88
|
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'reek/source/sexp_node'
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module Source
|
5
|
+
module SexpExtensions
|
6
|
+
module AndNode
|
7
|
+
def condition() self[1..2].tap {|b| b.extend SexpNode } end
|
8
|
+
end
|
9
|
+
|
10
|
+
module OrNode
|
11
|
+
def condition() self[1..2].tap {|b| b.extend SexpNode } end
|
12
|
+
end
|
13
|
+
|
14
|
+
module AttrasgnNode
|
15
|
+
def args() self[3] end
|
16
|
+
end
|
17
|
+
|
18
|
+
module CaseNode
|
19
|
+
def condition() self[1] end
|
20
|
+
end
|
21
|
+
|
22
|
+
module CallNode
|
23
|
+
def receiver() self[1] end
|
24
|
+
def method_name() self[2] end
|
25
|
+
def args() self[3..-1] end
|
26
|
+
def arg_names
|
27
|
+
args.map {|arg| arg[1]}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module CvarNode
|
32
|
+
def name() self[1] end
|
33
|
+
end
|
34
|
+
|
35
|
+
CvasgnNode = CvarNode
|
36
|
+
CvdeclNode = CvarNode
|
37
|
+
|
38
|
+
module LvarNode
|
39
|
+
def value() self[1] end
|
40
|
+
end
|
41
|
+
|
42
|
+
module MethodNode
|
43
|
+
def arg_names
|
44
|
+
@args ||= parameter_names.reject {|param| param.to_s =~ /^&/}
|
45
|
+
end
|
46
|
+
def parameter_names
|
47
|
+
@param_names ||= argslist[1..-1].map { |param| Sexp === param ? param[1] : param }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
module DefnNode
|
52
|
+
def name() self[1] end
|
53
|
+
def argslist() self[2] end
|
54
|
+
def body()
|
55
|
+
self[3..-1].extend SexpNode
|
56
|
+
end
|
57
|
+
include MethodNode
|
58
|
+
def full_name(outer)
|
59
|
+
prefix = outer == '' ? '' : "#{outer}#"
|
60
|
+
"#{prefix}#{name}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module DefsNode
|
65
|
+
def receiver() self[1] end
|
66
|
+
def name() self[2] end
|
67
|
+
def argslist() self[3] end
|
68
|
+
def body()
|
69
|
+
self[4..-1].extend SexpNode
|
70
|
+
end
|
71
|
+
include MethodNode
|
72
|
+
def full_name(outer)
|
73
|
+
prefix = outer == '' ? '' : "#{outer}#"
|
74
|
+
"#{prefix}#{SexpNode.format(receiver)}.#{name}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module IfNode
|
79
|
+
def condition() self[1] end
|
80
|
+
end
|
81
|
+
|
82
|
+
module IterNode
|
83
|
+
def call() self[1] end
|
84
|
+
def args() self[2] end
|
85
|
+
def block() self[3] end
|
86
|
+
def parameters() self[2] || [] end
|
87
|
+
def parameter_names
|
88
|
+
parameters[1..-1].to_a
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module LitNode
|
93
|
+
def value() self[1] end
|
94
|
+
end
|
95
|
+
|
96
|
+
module Colon2Node
|
97
|
+
def name
|
98
|
+
self[2]
|
99
|
+
end
|
100
|
+
|
101
|
+
def simple_name
|
102
|
+
if name.is_a? Colon2Node
|
103
|
+
name.simple_name
|
104
|
+
else
|
105
|
+
name
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
module ModuleNode
|
111
|
+
def name() self[1] end
|
112
|
+
|
113
|
+
def simple_name
|
114
|
+
Sexp === name ? name.simple_name : name
|
115
|
+
end
|
116
|
+
|
117
|
+
def full_name(outer)
|
118
|
+
prefix = outer == '' ? '' : "#{outer}::"
|
119
|
+
"#{prefix}#{text_name}"
|
120
|
+
end
|
121
|
+
def text_name
|
122
|
+
SexpNode.format(name)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module ClassNode
|
127
|
+
include ModuleNode
|
128
|
+
def superclass() self[2] end
|
129
|
+
end
|
130
|
+
|
131
|
+
module YieldNode
|
132
|
+
def args() self[1..-1] end
|
133
|
+
def arg_names
|
134
|
+
args.map {|arg| arg[1]}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Reek
|
2
|
+
module Source
|
3
|
+
#
|
4
|
+
# Extensions to +Sexp+ to allow +CodeParser+ to navigate the abstract
|
5
|
+
# syntax tree more easily.
|
6
|
+
#
|
7
|
+
module SexpNode
|
8
|
+
def self.format(expr)
|
9
|
+
case expr
|
10
|
+
when Sexp then expr.format_ruby
|
11
|
+
else expr.to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash
|
16
|
+
self.inspect.hash
|
17
|
+
end
|
18
|
+
|
19
|
+
def is_language_node?
|
20
|
+
Symbol === first
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_type?(type)
|
24
|
+
is_language_node? and first == type
|
25
|
+
end
|
26
|
+
|
27
|
+
def each_node(type, ignoring, &blk)
|
28
|
+
if block_given?
|
29
|
+
look_for(type, ignoring, &blk)
|
30
|
+
else
|
31
|
+
result = []
|
32
|
+
look_for(type, ignoring) {|exp| result << exp}
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def each_sexp
|
38
|
+
each { |elem| yield elem if Sexp === elem }
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Carries out a depth-first traversal of this syntax tree, yielding
|
43
|
+
# every Sexp of type +target_type+. The traversal ignores any node
|
44
|
+
# whose type is listed in the Array +ignoring+.
|
45
|
+
#
|
46
|
+
def look_for(target_type, ignoring, &blk)
|
47
|
+
each do |elem|
|
48
|
+
if Sexp === elem then
|
49
|
+
elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
blk.call(self) if first == target_type
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_ruby
|
56
|
+
Ruby2Ruby.new.process(deep_copy)
|
57
|
+
end
|
58
|
+
|
59
|
+
def deep_copy
|
60
|
+
Sexp.new(*map { |elem| Sexp === elem ? elem.deep_copy : elem })
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,191 +1,46 @@
|
|
1
|
+
require 'reek/source/sexp_node'
|
2
|
+
require 'reek/source/sexp_extensions'
|
3
|
+
|
1
4
|
module Reek
|
2
5
|
module Source
|
3
|
-
|
4
6
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
+
# Adorns an abstract syntax tree with mix-in modules to make accessing
|
8
|
+
# the tree more understandable and less implementation-dependent.
|
7
9
|
#
|
8
|
-
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
else expr.to_s
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def hash
|
17
|
-
self.inspect.hash
|
18
|
-
end
|
19
|
-
|
20
|
-
def is_language_node?
|
21
|
-
Symbol === first
|
22
|
-
end
|
23
|
-
|
24
|
-
def has_type?(type)
|
25
|
-
is_language_node? and first == type
|
26
|
-
end
|
27
|
-
|
28
|
-
def each_node(type, ignoring, &blk)
|
29
|
-
if block_given?
|
30
|
-
look_for(type, ignoring, &blk)
|
31
|
-
else
|
32
|
-
result = []
|
33
|
-
look_for(type, ignoring) {|exp| result << exp}
|
34
|
-
result
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
#
|
39
|
-
# Carries out a depth-first traversal of this syntax tree, yielding
|
40
|
-
# every Sexp of type +target_type+. The traversal ignores any node
|
41
|
-
# whose type is listed in the Array +ignoring+.
|
42
|
-
#
|
43
|
-
def look_for(target_type, ignoring, &blk)
|
44
|
-
each do |elem|
|
45
|
-
if Sexp === elem then
|
46
|
-
elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
blk.call(self) if first == target_type
|
50
|
-
end
|
51
|
-
|
52
|
-
def format_ruby
|
53
|
-
Ruby2Ruby.new.process(deep_copy)
|
54
|
-
end
|
55
|
-
|
56
|
-
def deep_copy
|
57
|
-
Sexp.new(*map { |elem| Sexp === elem ? elem.deep_copy : elem })
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
module SexpExtensions
|
62
|
-
module AttrasgnNode
|
63
|
-
def args() self[3] end
|
64
|
-
end
|
65
|
-
|
66
|
-
module CaseNode
|
67
|
-
def condition() self[1] end
|
68
|
-
end
|
69
|
-
|
70
|
-
module CallNode
|
71
|
-
def receiver() self[1] end
|
72
|
-
def method_name() self[2] end
|
73
|
-
def args() self[3..-1] end
|
74
|
-
def arg_names
|
75
|
-
args.map {|arg| arg[1]}
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
module CvarNode
|
80
|
-
def name() self[1] end
|
81
|
-
end
|
82
|
-
|
83
|
-
CvasgnNode = CvarNode
|
84
|
-
CvdeclNode = CvarNode
|
85
|
-
|
86
|
-
module MethodNode
|
87
|
-
def arg_names
|
88
|
-
@args ||= parameter_names.reject {|param| param.to_s =~ /^&/}
|
89
|
-
end
|
90
|
-
def parameter_names
|
91
|
-
@param_names ||= argslist[1..-1].map { |param| Sexp === param ? param[1] : param }
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
module DefnNode
|
96
|
-
def name() self[1] end
|
97
|
-
def argslist() self[2] end
|
98
|
-
def body()
|
99
|
-
self[3..-1].tap {|b| b.extend SexpNode }
|
100
|
-
end
|
101
|
-
include MethodNode
|
102
|
-
def full_name(outer)
|
103
|
-
prefix = outer == '' ? '' : "#{outer}#"
|
104
|
-
"#{prefix}#{name}"
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
module DefsNode
|
109
|
-
def receiver() self[1] end
|
110
|
-
def name() self[2] end
|
111
|
-
def argslist() self[3] end
|
112
|
-
def body()
|
113
|
-
self[4..-1].tap {|b| b.extend SexpNode }
|
114
|
-
end
|
115
|
-
include MethodNode
|
116
|
-
def full_name(outer)
|
117
|
-
prefix = outer == '' ? '' : "#{outer}#"
|
118
|
-
"#{prefix}#{SexpNode.format(receiver)}.#{name}"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
module IfNode
|
123
|
-
def condition() self[1] end
|
124
|
-
end
|
125
|
-
|
126
|
-
module IterNode
|
127
|
-
def call() self[1] end
|
128
|
-
def args() self[2] end
|
129
|
-
def block() self[3] end
|
130
|
-
def parameters() self[2] || [] end
|
131
|
-
def parameter_names
|
132
|
-
parameters[1..-1].to_a
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
module LitNode
|
137
|
-
def value() self[1] end
|
10
|
+
class TreeDresser
|
11
|
+
def initialize(extensions_module = SexpExtensions, node_module = SexpNode)
|
12
|
+
@extensions_module = extensions_module
|
13
|
+
@node_module = node_module
|
138
14
|
end
|
139
15
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
while Sexp === expr and expr[0] == :colon2
|
145
|
-
expr = expr[2]
|
146
|
-
end
|
147
|
-
expr
|
148
|
-
end
|
149
|
-
def full_name(outer)
|
150
|
-
prefix = outer == '' ? '' : "#{outer}::"
|
151
|
-
"#{prefix}#{text_name}"
|
152
|
-
end
|
153
|
-
def text_name
|
154
|
-
SexpNode.format(name)
|
155
|
-
end
|
16
|
+
def dress(sexp)
|
17
|
+
extend_sexp(sexp)
|
18
|
+
sexp.each_sexp { |sub| dress(sub) }
|
19
|
+
sexp
|
156
20
|
end
|
157
21
|
|
158
|
-
|
159
|
-
include ModuleNode
|
160
|
-
def superclass() self[2] end
|
161
|
-
end
|
22
|
+
private
|
162
23
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
24
|
+
def extend_sexp(sexp)
|
25
|
+
sexp.extend(@node_module)
|
26
|
+
extension_module = extension_for(sexp)
|
27
|
+
sexp.extend(extension_module) if extension_module
|
168
28
|
end
|
169
|
-
end
|
170
29
|
|
171
|
-
|
172
|
-
|
173
|
-
# the tree more understandable and less implementation-dependent.
|
174
|
-
#
|
175
|
-
class TreeDresser
|
176
|
-
|
177
|
-
def dress(sexp)
|
178
|
-
sexp.extend(SexpNode)
|
179
|
-
module_name = extensions_for(sexp.sexp_type)
|
180
|
-
if SexpExtensions.const_defined?(module_name)
|
181
|
-
sexp.extend(SexpExtensions.const_get(module_name))
|
182
|
-
end
|
183
|
-
sexp[0..-1].each { |sub| dress(sub) if Array === sub }
|
184
|
-
sexp
|
30
|
+
def extension_for(sexp)
|
31
|
+
extension_map[sexp.sexp_type]
|
185
32
|
end
|
186
33
|
|
187
|
-
def
|
188
|
-
|
34
|
+
def extension_map
|
35
|
+
@extension_map ||= begin
|
36
|
+
assoc = @extensions_module.constants.map { |const|
|
37
|
+
[
|
38
|
+
const.to_s.sub(/Node$/, '').downcase.to_sym,
|
39
|
+
@extensions_module.const_get(const)
|
40
|
+
]
|
41
|
+
}
|
42
|
+
Hash[assoc]
|
43
|
+
end
|
189
44
|
end
|
190
45
|
end
|
191
46
|
end
|