reek 1.3.6 → 1.3.7
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 -0
- data/README.md +11 -1
- data/config/defaults.reek +1 -0
- data/features/command_line_interface/options.feature +1 -0
- data/features/rake_task/rake_task.feature +3 -0
- data/features/ruby_api/api.feature +1 -3
- data/features/samples.feature +27 -20
- data/features/support/env.rb +2 -2
- data/lib/reek/cli/application.rb +0 -4
- data/lib/reek/cli/command_line.rb +10 -12
- data/lib/reek/cli/reek_command.rb +1 -1
- data/lib/reek/cli/report.rb +36 -8
- data/lib/reek/config_file_exception.rb +3 -0
- data/lib/reek/core/code_context.rb +18 -8
- data/lib/reek/core/code_parser.rb +65 -61
- data/lib/reek/core/method_context.rb +4 -0
- data/lib/reek/core/module_context.rb +2 -2
- data/lib/reek/core/smell_repository.rb +3 -0
- data/lib/reek/core/sniffer.rb +0 -1
- data/lib/reek/core/stop_context.rb +1 -1
- data/lib/reek/smells/attribute.rb +1 -1
- data/lib/reek/smells/control_parameter.rb +79 -45
- data/lib/reek/smells/data_clump.rb +1 -1
- data/lib/reek/smells/duplicate_method_call.rb +1 -1
- data/lib/reek/smells/long_parameter_list.rb +1 -1
- data/lib/reek/smells/long_yield_list.rb +1 -1
- data/lib/reek/smells/nested_iterators.rb +1 -1
- data/lib/reek/smells/nil_check.rb +10 -5
- data/lib/reek/smells/repeated_conditional.rb +1 -1
- data/lib/reek/smells/smell_detector.rb +2 -3
- data/lib/reek/smells/too_many_instance_variables.rb +1 -1
- data/lib/reek/smells/too_many_methods.rb +1 -1
- data/lib/reek/smells/too_many_statements.rb +1 -1
- data/lib/reek/smells/uncommunicative_method_name.rb +4 -4
- data/lib/reek/smells/uncommunicative_module_name.rb +4 -4
- data/lib/reek/smells/uncommunicative_parameter_name.rb +9 -9
- data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
- data/lib/reek/smells/unused_parameters.rb +2 -6
- data/lib/reek/smells/utility_function.rb +1 -1
- data/lib/reek/source/code_comment.rb +1 -1
- data/lib/reek/source/config_file.rb +9 -8
- data/lib/reek/source/sexp_extensions.rb +2 -2
- data/lib/reek/source/sexp_node.rb +8 -5
- data/lib/reek/source/source_repository.rb +5 -0
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +3 -2
- data/spec/reek/cli/report_spec.rb +38 -8
- data/spec/reek/core/code_context_spec.rb +35 -3
- data/spec/reek/core/module_context_spec.rb +1 -1
- data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_shared.rb +1 -2
- data/spec/reek/smells/too_many_statements_spec.rb +39 -25
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +44 -30
- data/spec/reek/smells/unused_parameters_spec.rb +15 -11
- data/spec/reek/source/sexp_extensions_spec.rb +2 -2
- data/spec/reek/source/sexp_node_spec.rb +0 -1
- data/spec/samples/ruby20_syntax.rb +1 -5
- metadata +172 -162
- data/lib/reek/cli/yaml_command.rb +0 -32
- data/lib/reek/core/hash_extensions.rb +0 -29
- data/spec/reek/cli/yaml_command_spec.rb +0 -47
- data/spec/reek/core/config_spec.rb +0 -38
@@ -29,7 +29,7 @@ module Reek
|
|
29
29
|
DEFAULT_MAX_ALLOWED_PARAMS = 3
|
30
30
|
|
31
31
|
def self.default_config
|
32
|
-
super.
|
32
|
+
super.merge(
|
33
33
|
MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
|
34
34
|
Core::SmellConfiguration::OVERRIDES_KEY => {
|
35
35
|
"initialize" => {MAX_ALLOWED_PARAMS_KEY => 5}
|
@@ -12,13 +12,9 @@ module Reek
|
|
12
12
|
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
13
13
|
|
14
14
|
def examine_context(ctx)
|
15
|
-
|
16
15
|
call_nodes = CallNodeFinder.new(ctx)
|
17
16
|
case_nodes = CaseNodeFinder.new(ctx)
|
18
|
-
|
19
|
-
smelly_cases = case_nodes.smelly
|
20
|
-
|
21
|
-
smelly_nodes = smelly_calls + smelly_cases
|
17
|
+
smelly_nodes = call_nodes.smelly + case_nodes.smelly
|
22
18
|
|
23
19
|
smelly_nodes.map do |node|
|
24
20
|
SmellWarning.new(SMELL_CLASS, ctx.full_name, Array(node.line),
|
@@ -27,6 +23,9 @@ module Reek
|
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
26
|
+
#
|
27
|
+
# A base class that allows to work on all nodes of a certain type.
|
28
|
+
#
|
30
29
|
class NodeFinder
|
31
30
|
SEXP_NIL = Sexp.new(:nil)
|
32
31
|
def initialize(ctx, type)
|
@@ -34,6 +33,9 @@ module Reek
|
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
36
|
+
#
|
37
|
+
# Find call nodes which perform a nil check.
|
38
|
+
#
|
37
39
|
class CallNodeFinder < NodeFinder
|
38
40
|
def initialize(ctx)
|
39
41
|
super(ctx, :call)
|
@@ -62,6 +64,9 @@ module Reek
|
|
62
64
|
end
|
63
65
|
end
|
64
66
|
|
67
|
+
#
|
68
|
+
# Finds when statements that perform a nil check.
|
69
|
+
#
|
65
70
|
class CaseNodeFinder < NodeFinder
|
66
71
|
CASE_NIL_NODE = Sexp.new(:array, SEXP_NIL)
|
67
72
|
|
@@ -7,7 +7,7 @@ module Reek
|
|
7
7
|
|
8
8
|
module ExcludeInitialize
|
9
9
|
def self.default_config
|
10
|
-
super.
|
10
|
+
super.merge(EXCLUDE_KEY => ['initialize'])
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -85,8 +85,7 @@ module Reek
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def config_for(ctx)
|
88
|
-
ctx.
|
89
|
-
# BUG: needs to consider smell class AND subclass
|
88
|
+
ctx.config_for(self.class)
|
90
89
|
end
|
91
90
|
end
|
92
91
|
end
|
@@ -37,7 +37,7 @@ module Reek
|
|
37
37
|
DEFAULT_ACCEPT_SET = []
|
38
38
|
|
39
39
|
def self.default_config
|
40
|
-
super.
|
40
|
+
super.merge(
|
41
41
|
REJECT_KEY => DEFAULT_REJECT_SET,
|
42
42
|
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
43
43
|
)
|
@@ -55,14 +55,14 @@ module Reek
|
|
55
55
|
def examine_context(ctx)
|
56
56
|
@reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
57
57
|
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
58
|
-
name = ctx.name
|
58
|
+
name = ctx.name.to_s
|
59
59
|
return [] if @accept_names.include?(ctx.full_name)
|
60
|
-
var = name.
|
60
|
+
var = name.gsub(/^[@\*\&]*/, '')
|
61
61
|
return [] if @accept_names.include?(var)
|
62
62
|
return [] unless @reject_names.detect {|patt| patt === var}
|
63
63
|
smell = SmellWarning.new('UncommunicativeName', ctx.full_name, [ctx.exp.line],
|
64
64
|
"has the name '#{name}'",
|
65
|
-
@source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name
|
65
|
+
@source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name})
|
66
66
|
[smell]
|
67
67
|
end
|
68
68
|
end
|
@@ -37,7 +37,7 @@ module Reek
|
|
37
37
|
DEFAULT_ACCEPT_SET = ['Inline::C']
|
38
38
|
|
39
39
|
def self.default_config
|
40
|
-
super.
|
40
|
+
super.merge(
|
41
41
|
REJECT_KEY => DEFAULT_REJECT_SET,
|
42
42
|
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
43
43
|
)
|
@@ -58,14 +58,14 @@ module Reek
|
|
58
58
|
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
59
59
|
exp = ctx.exp
|
60
60
|
full_name = ctx.full_name
|
61
|
-
name = exp.simple_name
|
61
|
+
name = exp.simple_name.to_s
|
62
62
|
return [] if @accept_names.include?(full_name)
|
63
|
-
var = name.
|
63
|
+
var = name.gsub(/^[@\*\&]*/, '')
|
64
64
|
return [] if @accept_names.include?(var)
|
65
65
|
return [] unless @reject_names.detect {|patt| patt === var}
|
66
66
|
smell = SmellWarning.new(SMELL_CLASS, full_name, [exp.line],
|
67
67
|
"has the name '#{name}'",
|
68
|
-
@source, SMELL_SUBCLASS, {MODULE_NAME_KEY => name
|
68
|
+
@source, SMELL_SUBCLASS, {MODULE_NAME_KEY => name})
|
69
69
|
[smell]
|
70
70
|
end
|
71
71
|
end
|
@@ -27,7 +27,7 @@ module Reek
|
|
27
27
|
# smelly names to be reported.
|
28
28
|
REJECT_KEY = 'reject'
|
29
29
|
|
30
|
-
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
|
30
|
+
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/, /^_/]
|
31
31
|
|
32
32
|
# The name of the config field that lists the specific names that are
|
33
33
|
# to be treated as exceptions; these names will not be reported as
|
@@ -37,7 +37,7 @@ module Reek
|
|
37
37
|
DEFAULT_ACCEPT_SET = []
|
38
38
|
|
39
39
|
def self.default_config
|
40
|
-
super.
|
40
|
+
super.merge(
|
41
41
|
REJECT_KEY => DEFAULT_REJECT_SET,
|
42
42
|
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
43
43
|
)
|
@@ -55,17 +55,17 @@ module Reek
|
|
55
55
|
def examine_context(ctx)
|
56
56
|
@reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
57
57
|
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
58
|
-
ctx.exp
|
59
|
-
|
58
|
+
context_expression = ctx.exp
|
59
|
+
context_expression.parameter_names.select do |name|
|
60
|
+
is_bad_name?(name) && ctx.uses_param?(name)
|
60
61
|
end.map do |name|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
smell
|
62
|
+
SmellWarning.new(SMELL_CLASS, ctx.full_name, [context_expression.line],
|
63
|
+
"has the parameter name '#{name}'",
|
64
|
+
@source, SMELL_SUBCLASS, {PARAMETER_NAME_KEY => name.to_s})
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
def is_bad_name?(name
|
68
|
+
def is_bad_name?(name)
|
69
69
|
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
70
70
|
return false if var == '*' or @accept_names.include?(var)
|
71
71
|
@reject_names.detect {|patt| patt === var}
|
@@ -37,7 +37,7 @@ module Reek
|
|
37
37
|
params(method_ctx).select do |param|
|
38
38
|
param = sanitized_param(param)
|
39
39
|
next if skip?(param)
|
40
|
-
|
40
|
+
!method_ctx.uses_param?(param)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -45,10 +45,6 @@ module Reek
|
|
45
45
|
anonymous_splat?(param) || marked_unused?(param)
|
46
46
|
end
|
47
47
|
|
48
|
-
def unused?(method_ctx, param)
|
49
|
-
!method_ctx.local_nodes(:lvar).include?(Sexp.new(:lvar, param.to_sym))
|
50
|
-
end
|
51
|
-
|
52
48
|
def params(method_ctx)
|
53
49
|
method_ctx.exp.arg_names || EMPTY_ARRAY
|
54
50
|
end
|
@@ -66,7 +62,7 @@ module Reek
|
|
66
62
|
end
|
67
63
|
|
68
64
|
def zsuper?(method_ctx)
|
69
|
-
method_ctx.exp.body.
|
65
|
+
method_ctx.exp.body.has_nested_node? :zsuper
|
70
66
|
end
|
71
67
|
|
72
68
|
def smell_warning(method_ctx, param)
|
@@ -9,7 +9,6 @@ module Reek
|
|
9
9
|
# any or all of the smell detectors.
|
10
10
|
#
|
11
11
|
class ConfigFile
|
12
|
-
@@bad_config_files = []
|
13
12
|
|
14
13
|
#
|
15
14
|
# Load the YAML config file from the supplied +file_path+.
|
@@ -39,7 +38,7 @@ module Reek
|
|
39
38
|
rescue
|
40
39
|
klass = nil
|
41
40
|
end
|
42
|
-
|
41
|
+
report_problem("\"#{name}\" is not a code smell") unless klass
|
43
42
|
klass
|
44
43
|
end
|
45
44
|
|
@@ -50,26 +49,28 @@ module Reek
|
|
50
49
|
#
|
51
50
|
def load
|
52
51
|
if File.size(@file_path) == 0
|
53
|
-
|
52
|
+
report_problem('Empty file')
|
54
53
|
return {}
|
55
54
|
end
|
56
55
|
|
57
56
|
begin
|
58
57
|
result = YAML.load_file(@file_path) || {}
|
59
|
-
rescue =>
|
60
|
-
error
|
58
|
+
rescue => error
|
59
|
+
report_error(error.to_s)
|
61
60
|
end
|
62
61
|
|
63
|
-
|
62
|
+
report_error('Not a hash') unless Hash === result
|
64
63
|
|
65
64
|
result
|
66
65
|
end
|
67
66
|
|
67
|
+
private
|
68
|
+
|
68
69
|
#
|
69
70
|
# Report invalid configuration file to standard
|
70
71
|
# Error.
|
71
72
|
#
|
72
|
-
def
|
73
|
+
def report_problem(reason)
|
73
74
|
$stderr.puts "Warning: #{message(reason)}"
|
74
75
|
end
|
75
76
|
|
@@ -77,7 +78,7 @@ module Reek
|
|
77
78
|
# Report invalid configuration file to standard
|
78
79
|
# Error.
|
79
80
|
#
|
80
|
-
def
|
81
|
+
def report_error(reason)
|
81
82
|
raise ConfigFileException.new message(reason)
|
82
83
|
end
|
83
84
|
|
@@ -4,11 +4,11 @@ module Reek
|
|
4
4
|
module Source
|
5
5
|
module SexpExtensions
|
6
6
|
module AndNode
|
7
|
-
def condition() self[1..2].tap {|
|
7
|
+
def condition() self[1..2].tap {|node| node.extend SexpNode } end
|
8
8
|
end
|
9
9
|
|
10
10
|
module OrNode
|
11
|
-
def condition() self[1..2].tap {|
|
11
|
+
def condition() self[1..2].tap {|node| node.extend SexpNode } end
|
12
12
|
end
|
13
13
|
|
14
14
|
module AttrasgnNode
|
@@ -43,15 +43,18 @@ module Reek
|
|
43
43
|
# every Sexp of type +target_type+. The traversal ignores any node
|
44
44
|
# whose type is listed in the Array +ignoring+.
|
45
45
|
#
|
46
|
-
def look_for(target_type, ignoring, &blk)
|
47
|
-
|
48
|
-
|
49
|
-
elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
|
50
|
-
end
|
46
|
+
def look_for(target_type, ignoring = [], &blk)
|
47
|
+
each_sexp do |elem|
|
48
|
+
elem.look_for(target_type, ignoring, &blk) unless ignoring.include?(elem.first)
|
51
49
|
end
|
52
50
|
blk.call(self) if first == target_type
|
53
51
|
end
|
54
52
|
|
53
|
+
def has_nested_node?(target_type)
|
54
|
+
look_for(target_type) { |elem| return true }
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
55
58
|
def format_ruby
|
56
59
|
Ruby2Ruby.new.process(deep_copy)
|
57
60
|
end
|
@@ -4,6 +4,11 @@ require 'reek/source/source_locator'
|
|
4
4
|
|
5
5
|
module Reek
|
6
6
|
module Source
|
7
|
+
#
|
8
|
+
# A collection of source code. If the collection is initialized with an array
|
9
|
+
# it is assumed to be a list of file paths. Otherwise it is assumed to be a
|
10
|
+
# single unit of Ruby source code.
|
11
|
+
#
|
7
12
|
class SourceRepository
|
8
13
|
def self.parse source
|
9
14
|
case source
|
data/lib/reek/version.rb
CHANGED
data/reek.gemspec
CHANGED
@@ -26,9 +26,10 @@ and reports any code smells it finds.
|
|
26
26
|
s.rubygems_version = %q{1.3.6}
|
27
27
|
s.summary = %q{Code smell detector for Ruby}
|
28
28
|
|
29
|
-
s.add_runtime_dependency(%q<ruby_parser>, ["~> 3.
|
29
|
+
s.add_runtime_dependency(%q<ruby_parser>, ["~> 3.3"])
|
30
30
|
s.add_runtime_dependency(%q<sexp_processor>)
|
31
|
-
s.add_runtime_dependency(%q<ruby2ruby>, ["~> 2.0.
|
31
|
+
s.add_runtime_dependency(%q<ruby2ruby>, ["~> 2.0.8"])
|
32
|
+
s.add_runtime_dependency(%q<rainbow>)
|
32
33
|
|
33
34
|
s.add_development_dependency(%q<bundler>, ["~> 1.1"])
|
34
35
|
s.add_development_dependency(%q<rake>)
|