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