reek 1.3.6 → 1.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +6 -0
  3. data/README.md +11 -1
  4. data/config/defaults.reek +1 -0
  5. data/features/command_line_interface/options.feature +1 -0
  6. data/features/rake_task/rake_task.feature +3 -0
  7. data/features/ruby_api/api.feature +1 -3
  8. data/features/samples.feature +27 -20
  9. data/features/support/env.rb +2 -2
  10. data/lib/reek/cli/application.rb +0 -4
  11. data/lib/reek/cli/command_line.rb +10 -12
  12. data/lib/reek/cli/reek_command.rb +1 -1
  13. data/lib/reek/cli/report.rb +36 -8
  14. data/lib/reek/config_file_exception.rb +3 -0
  15. data/lib/reek/core/code_context.rb +18 -8
  16. data/lib/reek/core/code_parser.rb +65 -61
  17. data/lib/reek/core/method_context.rb +4 -0
  18. data/lib/reek/core/module_context.rb +2 -2
  19. data/lib/reek/core/smell_repository.rb +3 -0
  20. data/lib/reek/core/sniffer.rb +0 -1
  21. data/lib/reek/core/stop_context.rb +1 -1
  22. data/lib/reek/smells/attribute.rb +1 -1
  23. data/lib/reek/smells/control_parameter.rb +79 -45
  24. data/lib/reek/smells/data_clump.rb +1 -1
  25. data/lib/reek/smells/duplicate_method_call.rb +1 -1
  26. data/lib/reek/smells/long_parameter_list.rb +1 -1
  27. data/lib/reek/smells/long_yield_list.rb +1 -1
  28. data/lib/reek/smells/nested_iterators.rb +1 -1
  29. data/lib/reek/smells/nil_check.rb +10 -5
  30. data/lib/reek/smells/repeated_conditional.rb +1 -1
  31. data/lib/reek/smells/smell_detector.rb +2 -3
  32. data/lib/reek/smells/too_many_instance_variables.rb +1 -1
  33. data/lib/reek/smells/too_many_methods.rb +1 -1
  34. data/lib/reek/smells/too_many_statements.rb +1 -1
  35. data/lib/reek/smells/uncommunicative_method_name.rb +4 -4
  36. data/lib/reek/smells/uncommunicative_module_name.rb +4 -4
  37. data/lib/reek/smells/uncommunicative_parameter_name.rb +9 -9
  38. data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
  39. data/lib/reek/smells/unused_parameters.rb +2 -6
  40. data/lib/reek/smells/utility_function.rb +1 -1
  41. data/lib/reek/source/code_comment.rb +1 -1
  42. data/lib/reek/source/config_file.rb +9 -8
  43. data/lib/reek/source/sexp_extensions.rb +2 -2
  44. data/lib/reek/source/sexp_node.rb +8 -5
  45. data/lib/reek/source/source_repository.rb +5 -0
  46. data/lib/reek/version.rb +1 -1
  47. data/reek.gemspec +3 -2
  48. data/spec/reek/cli/report_spec.rb +38 -8
  49. data/spec/reek/core/code_context_spec.rb +35 -3
  50. data/spec/reek/core/module_context_spec.rb +1 -1
  51. data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
  52. data/spec/reek/smells/smell_detector_shared.rb +1 -2
  53. data/spec/reek/smells/too_many_statements_spec.rb +39 -25
  54. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +44 -30
  55. data/spec/reek/smells/unused_parameters_spec.rb +15 -11
  56. data/spec/reek/source/sexp_extensions_spec.rb +2 -2
  57. data/spec/reek/source/sexp_node_spec.rb +0 -1
  58. data/spec/samples/ruby20_syntax.rb +1 -5
  59. metadata +172 -162
  60. data/lib/reek/cli/yaml_command.rb +0 -32
  61. data/lib/reek/core/hash_extensions.rb +0 -29
  62. data/spec/reek/cli/yaml_command_spec.rb +0 -47
  63. data/spec/reek/core/config_spec.rb +0 -38
@@ -49,7 +49,7 @@ module Reek
49
49
  DEFAULT_MIN_CLUMP_SIZE = 2
50
50
 
51
51
  def self.default_config
52
- super.adopt(
52
+ super.merge(
53
53
  MAX_COPIES_KEY => DEFAULT_MAX_COPIES,
54
54
  MIN_CLUMP_SIZE_KEY => DEFAULT_MIN_CLUMP_SIZE
55
55
  )
@@ -38,7 +38,7 @@ module Reek
38
38
  DEFAULT_ALLOW_CALLS = []
39
39
 
40
40
  def self.default_config
41
- super.adopt(
41
+ super.merge(
42
42
  MAX_ALLOWED_CALLS_KEY => DEFAULT_MAX_CALLS,
43
43
  ALLOW_CALLS_KEY => DEFAULT_ALLOW_CALLS
44
44
  )
@@ -29,7 +29,7 @@ module Reek
29
29
  DEFAULT_MAX_ALLOWED_PARAMS = 3
30
30
 
31
31
  def self.default_config
32
- super.adopt(
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}
@@ -24,7 +24,7 @@ module Reek
24
24
  PARAMETER_COUNT_KEY = 'parameter_count'
25
25
 
26
26
  def self.default_config
27
- super.adopt(
27
+ super.merge(
28
28
  MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS
29
29
  )
30
30
  end
@@ -29,7 +29,7 @@ module Reek
29
29
  DEFAULT_IGNORE_ITERATORS = []
30
30
 
31
31
  def self.default_config
32
- super.adopt(
32
+ super.merge(
33
33
  MAX_ALLOWED_NESTING_KEY => DEFAULT_MAX_ALLOWED_NESTING,
34
34
  IGNORE_ITERATORS_KEY => DEFAULT_IGNORE_ITERATORS
35
35
  )
@@ -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
- smelly_calls = call_nodes.smelly
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
 
@@ -37,7 +37,7 @@ module Reek
37
37
  DEFAULT_MAX_IFS = 2
38
38
 
39
39
  def self.default_config
40
- super.adopt(MAX_IDENTICAL_IFS_KEY => DEFAULT_MAX_IFS)
40
+ super.merge(MAX_IDENTICAL_IFS_KEY => DEFAULT_MAX_IFS)
41
41
  end
42
42
 
43
43
  #
@@ -7,7 +7,7 @@ module Reek
7
7
 
8
8
  module ExcludeInitialize
9
9
  def self.default_config
10
- super.adopt(EXCLUDE_KEY => ['initialize'])
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.config[self.class.name.split(/::/)[-1]] || {}
89
- # BUG: needs to consider smell class AND subclass
88
+ ctx.config_for(self.class)
90
89
  end
91
90
  end
92
91
  end
@@ -28,7 +28,7 @@ module Reek
28
28
  end
29
29
 
30
30
  def self.default_config
31
- super.adopt(
31
+ super.merge(
32
32
  MAX_ALLOWED_IVARS_KEY => DEFAULT_MAX_IVARS,
33
33
  EXCLUDE_KEY => []
34
34
  )
@@ -30,7 +30,7 @@ module Reek
30
30
  end
31
31
 
32
32
  def self.default_config
33
- super.adopt(
33
+ super.merge(
34
34
  MAX_ALLOWED_METHODS_KEY => DEFAULT_MAX_METHODS,
35
35
  EXCLUDE_KEY => []
36
36
  )
@@ -23,7 +23,7 @@ module Reek
23
23
  DEFAULT_MAX_STATEMENTS = 5
24
24
 
25
25
  def self.default_config
26
- super.adopt(
26
+ super.merge(
27
27
  MAX_ALLOWED_STATEMENTS_KEY => DEFAULT_MAX_STATEMENTS,
28
28
  EXCLUDE_KEY => ['initialize']
29
29
  )
@@ -37,7 +37,7 @@ module Reek
37
37
  DEFAULT_ACCEPT_SET = []
38
38
 
39
39
  def self.default_config
40
- super.adopt(
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.to_s.gsub(/^[@\*\&]*/, '')
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.to_s})
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.adopt(
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.to_s.gsub(/^[@\*\&]*/, '')
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.to_s})
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.adopt(
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.parameter_names.select do |name|
59
- is_bad_name?(name, ctx)
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
- smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
62
- "has the parameter name '#{name}'",
63
- @source, SMELL_SUBCLASS, {PARAMETER_NAME_KEY => name.to_s})
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, ctx)
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
  DEFAULT_ACCEPT_SET = ['_']
38
38
 
39
39
  def self.default_config
40
- super.adopt(
40
+ super.merge(
41
41
  REJECT_KEY => DEFAULT_REJECT_SET,
42
42
  ACCEPT_KEY => DEFAULT_ACCEPT_SET
43
43
  )
@@ -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
- unused?(method_ctx, param)
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.find_node :zsuper
65
+ method_ctx.exp.body.has_nested_node? :zsuper
70
66
  end
71
67
 
72
68
  def smell_warning(method_ctx, param)
@@ -51,7 +51,7 @@ module Reek
51
51
  [:defn]
52
52
  end
53
53
  def default_config
54
- super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
54
+ super.merge(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
55
55
  end
56
56
  end
57
57
 
@@ -11,7 +11,7 @@ module Reek
11
11
 
12
12
  def initialize(text)
13
13
  @config = Hash.new { |hash,key| hash[key] = {} }
14
- @text = text.gsub(CONFIG_REGEX) do |m|
14
+ @text = text.gsub(CONFIG_REGEX) do
15
15
  add_to_config($1, $2)
16
16
  ''
17
17
  end.gsub(/#/, '').gsub(/\n/, '').strip
@@ -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
- problem("\"#{name}\" is not a code smell") unless klass
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
- problem('Empty file')
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 => e
60
- error(e.to_s)
58
+ rescue => error
59
+ report_error(error.to_s)
61
60
  end
62
61
 
63
- error('Not a hash') unless Hash === result
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 problem(reason)
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 error(reason)
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 {|b| b.extend SexpNode } end
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 {|b| b.extend SexpNode } end
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
- each do |elem|
48
- if Sexp === elem then
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
@@ -1,3 +1,3 @@
1
1
  module Reek
2
- VERSION = '1.3.6'
2
+ VERSION = '1.3.7'
3
3
  end
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.2"])
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.7"])
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>)