reek 4.7.3 → 4.8.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -12
  3. data/.rubocop.yml +1 -0
  4. data/.travis.yml +4 -2
  5. data/CHANGELOG.md +10 -0
  6. data/Gemfile +3 -3
  7. data/README.md +19 -4
  8. data/lib/reek/ast/node.rb +37 -49
  9. data/lib/reek/ast/reference_collector.rb +2 -4
  10. data/lib/reek/ast/sexp_extensions/case.rb +1 -1
  11. data/lib/reek/ast/sexp_extensions/if.rb +8 -1
  12. data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
  13. data/lib/reek/ast/sexp_extensions/methods.rb +3 -5
  14. data/lib/reek/configuration/configuration_file_finder.rb +3 -3
  15. data/lib/reek/context/code_context.rb +4 -7
  16. data/lib/reek/context/method_context.rb +5 -10
  17. data/lib/reek/context/module_context.rb +3 -3
  18. data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +9 -9
  19. data/lib/reek/errors/bad_detector_in_comment_error.rb +7 -7
  20. data/lib/reek/errors/base_error.rb +3 -0
  21. data/lib/reek/errors/encoding_error.rb +16 -11
  22. data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +7 -7
  23. data/lib/reek/errors/incomprehensible_source_error.rb +20 -22
  24. data/lib/reek/examiner.rb +18 -14
  25. data/lib/reek/logging_error_handler.rb +7 -5
  26. data/lib/reek/smell_detectors/class_variable.rb +3 -10
  27. data/lib/reek/smell_detectors/duplicate_method_call.rb +1 -1
  28. data/lib/reek/smell_detectors/instance_variable_assumption.rb +1 -9
  29. data/lib/reek/smell_detectors/manual_dispatch.rb +1 -1
  30. data/lib/reek/smell_detectors/module_initialize.rb +1 -1
  31. data/lib/reek/smell_detectors/nested_iterators.rb +2 -1
  32. data/lib/reek/smell_detectors/too_many_constants.rb +1 -1
  33. data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +2 -2
  34. data/lib/reek/smell_detectors/utility_function.rb +1 -1
  35. data/lib/reek/source/source_code.rb +9 -23
  36. data/lib/reek/version.rb +1 -1
  37. data/reek.gemspec +2 -2
  38. data/spec/factories/factories.rb +2 -13
  39. data/spec/reek/ast/node_spec.rb +98 -5
  40. data/spec/reek/ast/reference_collector_spec.rb +1 -1
  41. data/spec/reek/ast/sexp_extensions_spec.rb +2 -2
  42. data/spec/reek/cli/application_spec.rb +39 -41
  43. data/spec/reek/cli/command/todo_list_command_spec.rb +2 -2
  44. data/spec/reek/code_comment_spec.rb +32 -32
  45. data/spec/reek/configuration/app_configuration_spec.rb +3 -3
  46. data/spec/reek/configuration/configuration_file_finder_spec.rb +1 -1
  47. data/spec/reek/configuration/directory_directives_spec.rb +3 -3
  48. data/spec/reek/configuration/excluded_paths_spec.rb +1 -1
  49. data/spec/reek/context/code_context_spec.rb +59 -95
  50. data/spec/reek/context/ghost_context_spec.rb +1 -1
  51. data/spec/reek/context/root_context_spec.rb +1 -1
  52. data/spec/reek/errors/base_error_spec.rb +13 -0
  53. data/spec/reek/examiner_spec.rb +72 -29
  54. data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
  55. data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
  56. data/spec/reek/report/xml_report_spec.rb +2 -2
  57. data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
  58. data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
  59. data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
  60. data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
  61. data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
  62. data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
  63. data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +3 -3
  64. data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
  65. data/spec/reek/smell_detectors/unused_private_method_spec.rb +9 -9
  66. data/spec/reek/smell_detectors/utility_function_spec.rb +5 -5
  67. data/spec/reek/smell_warning_spec.rb +8 -8
  68. data/spec/reek/source/source_code_spec.rb +5 -25
  69. data/spec/reek/source/source_locator_spec.rb +6 -6
  70. data/spec/reek/spec/should_reek_of_spec.rb +7 -7
  71. data/spec/reek/spec/should_reek_spec.rb +2 -2
  72. data/spec/reek/spec/smell_matcher_spec.rb +3 -3
  73. data/spec/reek/tree_dresser_spec.rb +11 -12
  74. data/spec/spec_helper.rb +3 -12
  75. metadata +10 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a71c30d4b1f39eee7c330e391cb1e80afa0a2d3b
4
- data.tar.gz: fad4cfa8b8d369ff2e5c6ac5a22f3be8abc03891
3
+ metadata.gz: 03a8001a3b22bcc9d88119e88b42854baec8a7e6
4
+ data.tar.gz: 51eeaee185f5d97ef76ba341a875aad0202c14ad
5
5
  SHA512:
6
- metadata.gz: 15725dcd429906aba55b5dd379f01b1bf5e6eb08dd8df814a8403752c1356481137e0804618a449010c1b6dbb90da5543f21cf9401d2faaacbb6254734b01f68
7
- data.tar.gz: 4a7a38b3b04059de3f93bb90ee14354fc0052f6c8a23ff3f96b8f456643087aa9f51aca3ff22758ef118f4c3fc05c741892a9658c512c0f0f171ee7cbbbfbd9a
6
+ metadata.gz: a8302cbf65eb954433fb21243998da37fdc1755a7a82f10a465f7205c9b697f8498b593584d3fbc857c6d8544bbd49441c42f62ea5049916ad2a02bdf7a6ef94
7
+ data.tar.gz: 3150c68090b560dbdd91e51dc520c63bc6d6367c3efa4772e64205969b22fb0cdf292ed297a3f09ef271f32b9594d9380bc817cc855021e003c641a561606143
@@ -1,16 +1,21 @@
1
- engines:
1
+ version: "2"
2
+ plugins:
2
3
  rubocop:
3
4
  enabled: true
4
5
  reek:
5
6
  enabled: true
6
- ratings:
7
- paths:
8
- - lib/**/*
9
- exclude_paths:
10
- - coverage/
11
- - docs/
12
- - features/
13
- - logo/
14
- - pkg/
15
- - spec/
16
- - tmp/
7
+ duplication:
8
+ enabled: true
9
+ config:
10
+ languages:
11
+ ruby:
12
+ mass_threshold: 35
13
+ exclude_patterns:
14
+ - coverage/
15
+ - docs/
16
+ - features/
17
+ - logo/
18
+ - pkg/
19
+ - samples/
20
+ - spec/
21
+ - tmp/
@@ -117,6 +117,7 @@ RSpec/MultipleExpectations:
117
117
  # FIXME: Update specs to avoid offenses
118
118
  RSpec/NestedGroups:
119
119
  Exclude:
120
+ - 'spec/reek/report/code_climate/code_climate_fingerprint_spec.rb'
120
121
  - 'spec/reek/cli/application_spec.rb'
121
122
 
122
123
  # rubocop-rspec expects a CodeClimate namespace to go with the code_climate directory.
@@ -2,6 +2,7 @@ sudo: false
2
2
  dist: trusty
3
3
  cache: bundler
4
4
  language: ruby
5
+ before_install: gem update --system
5
6
  bundler_args: --without debugging
6
7
  script: bundle exec rake ci
7
8
  rvm:
@@ -9,13 +10,14 @@ rvm:
9
10
  - 2.2
10
11
  - 2.3
11
12
  - 2.4
12
- - jruby-9.1.7.0
13
+ - 2.5
14
+ - jruby-9.1.15.0
13
15
  - jruby-head
14
16
  - ruby-head
15
17
  - rubinius-3
16
18
  matrix:
17
19
  allow_failures:
18
- - rvm: jruby-9.1.7.0
20
+ - rvm: jruby-9.1.15.0
19
21
  - rvm: jruby-head
20
22
  - rvm: ruby-head
21
23
  - rvm: rubinius-3
@@ -1,5 +1,15 @@
1
1
  # Change log
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 4.8.0 (2018-03-05)
6
+
7
+ * (pocke) Support Ruby 2.5
8
+ * (mvz) Print original exception class in certain error messages
9
+ * (mvz) Only include long exception message in CLI output
10
+ * (chastell) Add official support for Ruby 2.5
11
+ * (mvz) Do not detect ModuleInitialize for nested dynamic classes
12
+
3
13
  ## 4.7.3 (2017-11-05)
4
14
 
5
15
  * (mvz) Handle UTF-8 files in all locales
data/Gemfile CHANGED
@@ -9,15 +9,15 @@ group :development do
9
9
  gem 'aruba', '~> 0.14.0'
10
10
  gem 'ataru', '~> 0.2.0'
11
11
  gem 'cucumber', '~> 3.0'
12
- gem 'factory_girl', '~> 4.0'
12
+ gem 'factory_bot', '~> 4.0'
13
13
  gem 'rake', '~> 12.0'
14
14
  gem 'rspec', '~> 3.0'
15
15
  gem 'simplecov', '~> 0.15.0'
16
16
  gem 'yard', '~> 0.9.5'
17
17
 
18
18
  if RUBY_VERSION >= '2.3'
19
- gem 'rubocop', '~> 0.51.0'
20
- gem 'rubocop-rspec', '~> 1.19.0'
19
+ gem 'rubocop', '~> 0.52.0'
20
+ gem 'rubocop-rspec', '~> 1.20'
21
21
  end
22
22
 
23
23
  platforms :mri do
data/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  - [Overview](#overview)
8
8
  - [Quickstart](#quickstart)
9
9
  - [Example](#example)
10
- - [Supported rubies](#supported-rubies)
10
+ - [Supported Ruby versions](#supported-ruby-versions)
11
11
  - [Fixing Smell Warnings](#fixing-smell-warnings)
12
12
  - [Sources](#sources)
13
13
  - [Code smells](#code-smells)
@@ -93,16 +93,17 @@ demo.rb -- 2 warnings:
93
93
  [5]:UncommunicativeVariableName: Smelly#x has the variable name 'y' [https://github.com/troessner/reek/blob/master/docs/Uncommunicative-Variable-Name.md]
94
94
  ```
95
95
 
96
- ## Supported rubies
96
+ ## Supported Ruby versions
97
97
 
98
- Reek is officially running on the following MRI rubies:
98
+ Reek is officially supported for the following CRuby versions:
99
99
 
100
100
  - 2.1
101
101
  - 2.2
102
102
  - 2.3
103
103
  - 2.4
104
+ - 2.5
104
105
 
105
- Other rubies like Rubinius or JRuby are not officially supported but should work as well.
106
+ Other Ruby implementations (like Rubinius or JRuby) are not officially supported but should work as well.
106
107
 
107
108
  ## Fixing Smell Warnings
108
109
 
@@ -528,6 +529,20 @@ If you don't feel like getting your hands dirty with code there are still other
528
529
  * Open up an [issue](https://github.com/troessner/reek/issues) and report bugs
529
530
  * Suggest other improvements like additional smells for instance
530
531
 
532
+ ### Running Code Climate locally
533
+
534
+ If you run into Code Climate issues (e.g., go over code duplication
535
+ threshold) you might want to be able to run Code Climate against
536
+ the Reek codebase locally. To do this, you need to do the following:
537
+
538
+ * [install Docker CE](https://docs.docker.com/engine/installation/)
539
+ * [install Code Climate CLI](https://github.com/codeclimate/codeclimate#installation)
540
+ * `gem install codeclimate`
541
+ * `codeclimate engines:install`
542
+
543
+ Now you can run various Code Climate engines,
544
+ e.g., `codeclimate analyze -e duplication`
545
+
531
546
  ## Output formats
532
547
 
533
548
  Reek supports 5 output formats:
@@ -33,55 +33,43 @@ module Reek
33
33
  loc && loc.line
34
34
  end
35
35
 
36
+ def name
37
+ to_s
38
+ end
39
+
36
40
  #
37
- # Carries out a depth-first traversal of this syntax tree, yielding
38
- # every Sexp of type `target_type`. The traversal ignores any node
39
- # whose type is listed in the Array `ignoring`.
40
- # Takes a block as well.
41
- #
42
- # target_type - the type to look for, e.g. :send, :block
43
- # ignoring - types to ignore, e.g. [:casgn, :class, :module]
44
- # blk - block to execute for every hit. Gets passed in the matching element itself.
41
+ # Carries out a depth-first traversal of this syntax tree, yielding every
42
+ # Sexp of the searched for type or types. The traversal stops at any node
43
+ # whose type is listed in `ignoring`.
45
44
  #
46
- # Examples:
47
- # context.each_node(:send, [:mlhs]) do |call_node| .... end
48
- # context.each_node(:lvar).any? { |it| it.var_name == 'something' }
45
+ # If a type is searched for *and* listed in ignoring, it will be yielded
46
+ # but traversal will not continue to its children.
49
47
  #
50
- # Returns an array with all matching nodes.
51
- # TODO: without a block, this doesn't do what one might expect
52
- def each_node(target_type, ignoring = [], &blk)
53
- if block_given?
54
- look_for_type(target_type, ignoring, &blk)
55
- else
56
- result = []
57
- look_for_type(target_type, ignoring) { |exp| result << exp }
58
- result
59
- end
60
- end
61
-
48
+ # If the root's type is ignored, traversal does not stop, unless the root
49
+ # is of a target type.
62
50
  #
63
- # Carries out a depth-first traversal of this syntax tree, yielding
64
- # every Sexp of type `target_type`. The traversal ignores any node
65
- # whose type is listed in the Array `ignoring`, including the top node.
66
51
  # Takes a block as well.
67
52
  #
68
- # target_types - the types to look for, e.g. [:send, :block]
69
- # ignoring - types to ignore, e.g. [:casgn, :class, :module]
70
- # blk - block to execute for every hit
53
+ # @param target_types [Symbol, Array<Symbol>] the type or types to look
54
+ # for
55
+ # @param ignoring [Array<Symbol>] types to ignore
56
+ # @param blk block to execute for every hit. Gets passed in the
57
+ # matching element itself.
71
58
  #
72
- # Examples:
73
- # exp.find_nodes([:block]).flat_map do |elem| ... end
59
+ # @example
60
+ # node.each_node(:send, [:mlhs]) do |call_node| .... end
61
+ # node.each_node(:lvar).any? { |it| it.var_name == 'something' }
62
+ # node.each_node([:block]).flat_map do |elem| ... end
74
63
  #
75
64
  # Returns an array with all matching nodes.
76
- def find_nodes(target_types, ignoring = [])
77
- result = []
78
- look_for_types(target_types, ignoring) { |exp| result << exp }
79
- result
65
+ def each_node(target_types, ignoring = [], &blk)
66
+ return enum_for(:each_node, target_types, ignoring) unless block_given?
67
+
68
+ look_for(Array(target_types), ignoring, &blk)
80
69
  end
81
70
 
82
71
  def contains_nested_node?(target_type)
83
- look_for_type(target_type) { |_elem| return true }
84
- false
72
+ each_node(target_type).any?
85
73
  end
86
74
 
87
75
  # :reek:DuplicateMethodCall { max_calls: 2 } is ok for lines.first
@@ -120,22 +108,22 @@ module Reek
120
108
  protected
121
109
 
122
110
  # See ".each_node" for documentation.
123
- def look_for_type(target_type, ignoring = [], &blk)
111
+ def look_for(target_types, ignoring, &blk)
112
+ if target_types.include? type
113
+ yield self
114
+ return if ignoring.include?(type)
115
+ end
124
116
  each_sexp do |elem|
125
- elem.look_for_type(target_type, ignoring, &blk) unless ignoring.include?(elem.type)
117
+ elem.look_for_recurse(target_types, ignoring, &blk)
126
118
  end
127
- yield self if type == target_type
128
119
  end
129
120
 
130
- # See ".find_nodes" for documentation.
131
- def look_for_types(target_types, ignoring = [], &blk)
132
- return if ignoring.include?(type)
133
- if target_types.include? type
134
- yield self
135
- else
136
- each_sexp do |elem|
137
- elem.look_for_types(target_types, ignoring, &blk)
138
- end
121
+ # See ".each_node" for documentation.
122
+ def look_for_recurse(target_types, ignoring, &blk)
123
+ yield self if target_types.include? type
124
+ return if ignoring.include? type
125
+ each_sexp do |elem|
126
+ elem.look_for_recurse(target_types, ignoring, &blk)
139
127
  end
140
128
  end
141
129
 
@@ -12,7 +12,7 @@ module Reek
12
12
  end
13
13
 
14
14
  def num_refs_to_self
15
- (explicit_self_calls + implicit_self_calls).size
15
+ (explicit_self_calls.to_a + implicit_self_calls.to_a).size
16
16
  end
17
17
 
18
18
  private
@@ -20,9 +20,7 @@ module Reek
20
20
  attr_reader :ast
21
21
 
22
22
  def explicit_self_calls
23
- [:self, :super, :zsuper, :ivar, :ivasgn].flat_map do |node_type|
24
- ast.each_node(node_type)
25
- end
23
+ ast.each_node([:self, :super, :zsuper, :ivar, :ivasgn])
26
24
  end
27
25
 
28
26
  def implicit_self_calls
@@ -10,7 +10,7 @@ module Reek
10
10
  end
11
11
 
12
12
  def body_nodes(type, ignoring = [])
13
- children[1..-1].compact.flat_map { |child| child.find_nodes(type, ignoring) }
13
+ children[1..-1].compact.flat_map { |child| child.each_node(type, ignoring).to_a }
14
14
  end
15
15
 
16
16
  def else_body
@@ -9,8 +9,15 @@ module Reek
9
9
  children.first
10
10
  end
11
11
 
12
+ # :reek:FeatureEnvy
12
13
  def body_nodes(type, ignoring = [])
13
- children[1..-1].compact.flat_map { |child| child.find_nodes(type, ignoring) }
14
+ children[1..-1].compact.flat_map do |child|
15
+ if ignoring.include? child.type
16
+ []
17
+ else
18
+ child.each_node(type, ignoring).to_a
19
+ end
20
+ end
14
21
  end
15
22
  end
16
23
  end
@@ -10,7 +10,7 @@ module Reek
10
10
  end
11
11
 
12
12
  def body_nodes(type, ignoring = [])
13
- children[1].find_nodes type, ignoring
13
+ children[1].each_node type, ignoring
14
14
  end
15
15
  end
16
16
 
@@ -30,11 +30,9 @@ module Reek
30
30
  end
31
31
 
32
32
  def body_nodes(types, ignoring = [])
33
- if body
34
- body.find_nodes(types, ignoring)
35
- else
36
- []
37
- end
33
+ return [] unless body
34
+ return [] if ignoring.include?(body.type)
35
+ body.each_node(types, ignoring | types)
38
36
  end
39
37
  end
40
38
 
@@ -20,8 +20,8 @@ module Reek
20
20
  module ConfigurationFileFinder
21
21
  TOO_MANY_CONFIGURATION_FILES_MESSAGE = <<-MESSAGE.freeze
22
22
 
23
- Error: Found multiple configuration files %s
24
- while scanning directory %s.
23
+ Error: Found multiple configuration files %<files>s
24
+ while scanning directory %<directory>s.
25
25
 
26
26
  Reek supports only one configuration file. You have 2 options now:
27
27
  1) Remove all offending files.
@@ -111,7 +111,7 @@ module Reek
111
111
  #
112
112
  def escalate_too_many_configuration_files(found, directory)
113
113
  offensive_files = found.map { |file| "'#{file.basename}'" }.join(', ')
114
- warn format(TOO_MANY_CONFIGURATION_FILES_MESSAGE, offensive_files, directory)
114
+ warn format(TOO_MANY_CONFIGURATION_FILES_MESSAGE, files: offensive_files, directory: directory)
115
115
  exit 1
116
116
  end
117
117
  end
@@ -19,7 +19,6 @@ module Reek
19
19
  class CodeContext
20
20
  include Enumerable
21
21
  extend Forwardable
22
- delegate each_node: :exp
23
22
  delegate [:name, :type] => :exp
24
23
 
25
24
  attr_reader :children, :parent, :exp, :statement_counter
@@ -40,12 +39,12 @@ module Reek
40
39
  # @yield block that is executed for every node.
41
40
  #
42
41
  def local_nodes(type, ignored = [], &blk)
43
- ignored += [:casgn, :class, :module]
44
- each_node(type, ignored, &blk)
42
+ ignored |= [:class, :module]
43
+ exp.each_node(type, ignored, &blk)
45
44
  end
46
45
 
47
46
  # Iterate over `self` and child contexts.
48
- # The main difference (among others) to `each_node` is that we are traversing
47
+ # The main difference (among others) to `local_nodes` is that we are traversing
49
48
  # `CodeContexts` here, not AST nodes (see `Reek::AST::Node`).
50
49
  #
51
50
  # @yield block that is executed for every node.
@@ -119,9 +118,7 @@ module Reek
119
118
  line = exp.line
120
119
  case type
121
120
  when :lvar, :lvasgn
122
- unless exp.object_creation_call?
123
- refs.record_reference(name: receiver.name, line: line)
124
- end
121
+ refs.record_reference(name: receiver.name, line: line) unless exp.object_creation_call?
125
122
  when :self
126
123
  refs.record_reference(name: :self, line: line)
127
124
  end
@@ -23,21 +23,16 @@ module Reek
23
23
  end
24
24
 
25
25
  def uses_param?(param)
26
- # local_nodes(:lvasgn) catches:
26
+ # :lvasgn catches:
27
+ #
27
28
  # def foo(bar); bar += 1; end
28
- # In this example there is no `lvar` node present.
29
29
  #
30
- # local_nodes(:lvar) catches:
30
+ # :lvar catches:
31
+ #
31
32
  # def foo(bar); other(bar); end
32
33
  # def foo(bar); tmp = other(bar); tmp[0]; end
33
34
  #
34
- # Note that in the last example the `lvar` node for `bar` is part of an `lvasgn` node for `tmp`.
35
- # This means that if we would just search for [:lvar, :lvasgn]
36
- # (e.g. via Reek::AST::Node#find_nodes) this would fail for this example since we would
37
- # stop at the `lvasgn` and not detect the contained `lvar`.
38
- # Hence we first get all `lvar` nodes followed by all `lvasgn` nodes.
39
- #
40
- (local_nodes(:lvar) + local_nodes(:lvasgn)).find { |node| node.var_name == param.name }
35
+ local_nodes([:lvar, :lvasgn]).find { |node| node.var_name == param.name }
41
36
  end
42
37
 
43
38
  # :reek:FeatureEnvy
@@ -43,9 +43,9 @@ module Reek
43
43
  AttributeContext
44
44
  end
45
45
 
46
- def defined_instance_methods(visibility: :public)
46
+ def defined_instance_methods(visibility: :any)
47
47
  instance_method_children.select do |context|
48
- context.visibility == visibility
48
+ visibility == :any || context.visibility == visibility
49
49
  end
50
50
  end
51
51
 
@@ -59,7 +59,7 @@ module Reek
59
59
  # @deprecated use `defined_instance_methods` instead
60
60
  #
61
61
  def node_instance_methods
62
- local_nodes(:def)
62
+ local_nodes(:def).to_a
63
63
  end
64
64
 
65
65
  def descriptively_commented?