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.
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>)