reek 3.10.1 → 3.10.2

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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +26 -25
  4. data/.simplecov +10 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +3 -2
  7. data/README.md +11 -0
  8. data/bin/code_climate_reek +1 -1
  9. data/docs/Feature-Envy.md +1 -1
  10. data/docs/style-guide.md +1 -7
  11. data/lib/reek/ast/ast_node_class_map.rb +1 -1
  12. data/lib/reek/ast/node.rb +4 -4
  13. data/lib/reek/ast/object_refs.rb +1 -3
  14. data/lib/reek/ast/reference_collector.rb +2 -4
  15. data/lib/reek/ast/sexp_extensions/send.rb +1 -1
  16. data/lib/reek/ast/sexp_extensions/variables.rb +1 -1
  17. data/lib/reek/cli/application.rb +3 -12
  18. data/lib/reek/cli/command.rb +1 -5
  19. data/lib/reek/cli/option_interpreter.rb +5 -2
  20. data/lib/reek/cli/reek_command.rb +5 -1
  21. data/lib/reek/cli/warning_collector.rb +1 -2
  22. data/lib/reek/code_comment.rb +3 -3
  23. data/lib/reek/configuration/app_configuration.rb +3 -3
  24. data/lib/reek/context/attribute_context.rb +3 -1
  25. data/lib/reek/context/code_context.rb +2 -3
  26. data/lib/reek/context/statement_counter.rb +4 -2
  27. data/lib/reek/context/visibility_tracker.rb +6 -6
  28. data/lib/reek/context_builder.rb +7 -6
  29. data/lib/reek/examiner.rb +2 -1
  30. data/lib/reek/rake/task.rb +1 -2
  31. data/lib/reek/report.rb +4 -4
  32. data/lib/reek/report/code_climate/code_climate_formatter.rb +2 -3
  33. data/lib/reek/report/formatter.rb +4 -3
  34. data/lib/reek/report/report.rb +2 -2
  35. data/lib/reek/smells/control_parameter.rb +4 -4
  36. data/lib/reek/smells/data_clump.rb +4 -4
  37. data/lib/reek/smells/duplicate_method_call.rb +5 -5
  38. data/lib/reek/smells/long_parameter_list.rb +1 -1
  39. data/lib/reek/smells/long_yield_list.rb +1 -1
  40. data/lib/reek/smells/nested_iterators.rb +5 -5
  41. data/lib/reek/smells/nil_check.rb +1 -1
  42. data/lib/reek/smells/repeated_conditional.rb +1 -1
  43. data/lib/reek/smells/smell_configuration.rb +4 -6
  44. data/lib/reek/smells/smell_detector.rb +4 -3
  45. data/lib/reek/smells/smell_repository.rb +2 -2
  46. data/lib/reek/smells/smell_warning.rb +1 -1
  47. data/lib/reek/smells/too_many_instance_variables.rb +1 -1
  48. data/lib/reek/smells/too_many_methods.rb +1 -1
  49. data/lib/reek/smells/too_many_statements.rb +1 -1
  50. data/lib/reek/smells/uncommunicative_method_name.rb +4 -4
  51. data/lib/reek/smells/uncommunicative_module_name.rb +4 -4
  52. data/lib/reek/smells/uncommunicative_parameter_name.rb +4 -4
  53. data/lib/reek/smells/uncommunicative_variable_name.rb +5 -5
  54. data/lib/reek/source/source_code.rb +3 -3
  55. data/lib/reek/source/source_locator.rb +3 -4
  56. data/lib/reek/spec/should_reek.rb +4 -2
  57. data/lib/reek/spec/should_reek_of.rb +4 -4
  58. data/lib/reek/spec/should_reek_only_of.rb +1 -1
  59. data/lib/reek/spec/smell_matcher.rb +1 -1
  60. data/lib/reek/tree_dresser.rb +1 -1
  61. data/lib/reek/version.rb +1 -1
  62. data/reek.gemspec +1 -2
  63. data/spec/reek/cli/application_spec.rb +31 -0
  64. data/spec/reek/cli/reek_command_spec.rb +45 -0
  65. data/spec/reek/rake/task_spec.rb +33 -0
  66. data/spec/reek/smells/utility_function_spec.rb +1 -1
  67. data/spec/spec_helper.rb +3 -2
  68. data/tasks/configuration.rake +1 -1
  69. data/tasks/test.rake +1 -1
  70. metadata +11 -15
@@ -17,15 +17,14 @@ module Reek
17
17
  # :reek:UnusedPrivateMethod: { exclude: [ inherited, smell_warning ] }
18
18
  class SmellDetector
19
19
  attr_reader :config
20
- private_attr_accessor :smells_found
21
20
  # The name of the config field that lists the names of code contexts
22
21
  # that should not be checked. Add this field to the config for each
23
22
  # smell that should ignore this code element.
24
- EXCLUDE_KEY = 'exclude'
23
+ EXCLUDE_KEY = 'exclude'.freeze
25
24
 
26
25
  # The default value for the +EXCLUDE_KEY+ if it isn't specified
27
26
  # in any configuration file.
28
- DEFAULT_EXCLUDE_SET = []
27
+ DEFAULT_EXCLUDE_SET = [].freeze
29
28
 
30
29
  def initialize(config = {})
31
30
  @config = SmellConfiguration.new self.class.default_config.merge(config)
@@ -61,6 +60,8 @@ module Reek
61
60
 
62
61
  private
63
62
 
63
+ attr_accessor :smells_found
64
+
64
65
  def enabled_for?(context)
65
66
  config.enabled? && config_for(context)[SmellConfiguration::ENABLED_KEY] != false
66
67
  end
@@ -8,8 +8,6 @@ module Reek
8
8
  # Contains all the existing smells and exposes operations on them.
9
9
  #
10
10
  class SmellRepository
11
- private_attr_reader :configuration, :smell_types, :detectors
12
-
13
11
  # @return [Array<Reek::Smells::SmellDetector>] All known SmellDetectors
14
12
  # e.g. [Reek::Smells::BooleanParameter, Reek::Smells::ClassVariable].
15
13
  def self.smell_types
@@ -51,6 +49,8 @@ module Reek
51
49
 
52
50
  private
53
51
 
52
+ attr_reader :configuration, :smell_types, :detectors
53
+
54
54
  def configuration_for(klass)
55
55
  configuration.fetch klass, {}
56
56
  end
@@ -25,7 +25,7 @@ module Reek
25
25
  #
26
26
  # :reek:LongParameterList: { max_params: 6 }
27
27
  def initialize(smell_detector, context: '', lines: raise, message: raise,
28
- source: raise, parameters: {})
28
+ source: raise, parameters: {})
29
29
  @smell_detector = smell_detector
30
30
  @source = source
31
31
  @context = context.to_s
@@ -14,7 +14,7 @@ module Reek
14
14
  class TooManyInstanceVariables < SmellDetector
15
15
  # The name of the config field that sets the maximum number of instance
16
16
  # variables permitted in a class.
17
- MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
17
+ MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'.freeze
18
18
  DEFAULT_MAX_IVARS = 4
19
19
 
20
20
  def self.smell_category
@@ -16,7 +16,7 @@ module Reek
16
16
  class TooManyMethods < SmellDetector
17
17
  # The name of the config field that sets the maximum number of methods
18
18
  # permitted in a class.
19
- MAX_ALLOWED_METHODS_KEY = 'max_methods'
19
+ MAX_ALLOWED_METHODS_KEY = 'max_methods'.freeze
20
20
  DEFAULT_MAX_METHODS = 15
21
21
 
22
22
  def self.smell_category
@@ -12,7 +12,7 @@ module Reek
12
12
  class TooManyStatements < SmellDetector
13
13
  # The name of the config field that sets the maximum number of
14
14
  # statements permitted in any method.
15
- MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
15
+ MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'.freeze
16
16
  DEFAULT_MAX_STATEMENTS = 5
17
17
 
18
18
  def self.smell_category
@@ -18,10 +18,10 @@ module Reek
18
18
  #
19
19
  # See {file:docs/Uncommunicative-Method-Name.md} for details.
20
20
  class UncommunicativeMethodName < SmellDetector
21
- REJECT_KEY = 'reject'
22
- ACCEPT_KEY = 'accept'
23
- DEFAULT_REJECT_PATTERNS = [/^[a-z]$/, /[0-9]$/, /[A-Z]/]
24
- DEFAULT_ACCEPT_NAMES = []
21
+ REJECT_KEY = 'reject'.freeze
22
+ ACCEPT_KEY = 'accept'.freeze
23
+ DEFAULT_REJECT_PATTERNS = [/^[a-z]$/, /[0-9]$/, /[A-Z]/].freeze
24
+ DEFAULT_ACCEPT_NAMES = [].freeze
25
25
 
26
26
  def self.smell_category
27
27
  'UncommunicativeName'
@@ -20,14 +20,14 @@ module Reek
20
20
  class UncommunicativeModuleName < SmellDetector
21
21
  # The name of the config field that lists the regexps of
22
22
  # smelly names to be reported.
23
- REJECT_KEY = 'reject'
24
- DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/]
23
+ REJECT_KEY = 'reject'.freeze
24
+ DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/].freeze
25
25
 
26
26
  # The name of the config field that lists the specific names that are
27
27
  # to be treated as exceptions; these names will not be reported as
28
28
  # uncommunicative.
29
- ACCEPT_KEY = 'accept'
30
- DEFAULT_ACCEPT_NAMES = []
29
+ ACCEPT_KEY = 'accept'.freeze
30
+ DEFAULT_ACCEPT_NAMES = [].freeze
31
31
 
32
32
  def self.smell_category
33
33
  'UncommunicativeName'
@@ -18,11 +18,11 @@ module Reek
18
18
  #
19
19
  # See {file:docs/Uncommunicative-Parameter-Name.md} for details.
20
20
  class UncommunicativeParameterName < SmellDetector
21
- REJECT_KEY = 'reject'
22
- DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/, /[A-Z]/, /^_/]
21
+ REJECT_KEY = 'reject'.freeze
22
+ DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/, /[A-Z]/, /^_/].freeze
23
23
 
24
- ACCEPT_KEY = 'accept'
25
- DEFAULT_ACCEPT_NAMES = []
24
+ ACCEPT_KEY = 'accept'.freeze
25
+ DEFAULT_ACCEPT_NAMES = [].freeze
26
26
 
27
27
  def self.smell_category
28
28
  'UncommunicativeName'
@@ -22,14 +22,14 @@ module Reek
22
22
  class UncommunicativeVariableName < SmellDetector
23
23
  # The name of the config field that lists the regexps of
24
24
  # smelly names to be reported.
25
- REJECT_KEY = 'reject'
26
- DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
25
+ REJECT_KEY = 'reject'.freeze
26
+ DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/].freeze
27
27
 
28
28
  # The name of the config field that lists the specific names that are
29
29
  # to be treated as exceptions; these names will not be reported as
30
30
  # uncommunicative.
31
- ACCEPT_KEY = 'accept'
32
- DEFAULT_ACCEPT_SET = ['_']
31
+ ACCEPT_KEY = 'accept'.freeze
32
+ DEFAULT_ACCEPT_SET = ['_'].freeze
33
33
 
34
34
  def self.smell_category
35
35
  'UncommunicativeName'
@@ -130,7 +130,7 @@ module Reek
130
130
 
131
131
  private
132
132
 
133
- private_attr_accessor :accept_names, :reject_names
133
+ attr_accessor :accept_names, :reject_names
134
134
  end
135
135
  end
136
136
  end
@@ -11,8 +11,8 @@ module Reek
11
11
  # A +Source+ object represents a chunk of Ruby source code.
12
12
  #
13
13
  class SourceCode
14
- IO_IDENTIFIER = 'STDIN'
15
- STRING_IDENTIFIER = 'string'
14
+ IO_IDENTIFIER = 'STDIN'.freeze
15
+ STRING_IDENTIFIER = 'string'.freeze
16
16
 
17
17
  attr_reader :origin
18
18
 
@@ -94,7 +94,7 @@ module Reek
94
94
 
95
95
  private
96
96
 
97
- private_attr_reader :parser, :source
97
+ attr_reader :parser, :source
98
98
  end
99
99
  end
100
100
  end
@@ -1,4 +1,3 @@
1
- require 'private_attr/everywhere'
2
1
  require 'find'
3
2
  require 'pathname'
4
3
 
@@ -29,7 +28,7 @@ module Reek
29
28
 
30
29
  private
31
30
 
32
- private_attr_reader :configuration, :paths
31
+ attr_reader :configuration, :paths
33
32
 
34
33
  # :reek:TooManyStatements: { max_statements: 7 }
35
34
  # :reek:NestedIterators: { max_allowed_nesting: 2 }
@@ -43,8 +42,8 @@ module Reek
43
42
  given_path.find do |path|
44
43
  if path.directory?
45
44
  ignore_path?(path) ? Find.prune : next
46
- else
47
- relevant_paths << path if ruby_file?(path)
45
+ elsif ruby_file?(path)
46
+ relevant_paths << path
48
47
  end
49
48
  end
50
49
  end
@@ -25,8 +25,10 @@ module Reek
25
25
  "Expected no smells, but got:\n#{rpt}"
26
26
  end
27
27
 
28
- private_attr_reader :configuration
29
- private_attr_accessor :examiner
28
+ private
29
+
30
+ attr_reader :configuration
31
+ attr_accessor :examiner
30
32
  end
31
33
  end
32
34
  end
@@ -10,10 +10,6 @@ module Reek
10
10
  class ShouldReekOf
11
11
  attr_reader :failure_message, :failure_message_when_negated
12
12
 
13
- private_attr_reader :configuration, :smell_category, :smell_details
14
- private_attr_writer :failure_message, :failure_message_when_negated
15
- private_attr_accessor :examiner
16
-
17
13
  def initialize(smell_category,
18
14
  smell_details = {},
19
15
  configuration = Configuration::AppConfiguration.default)
@@ -30,6 +26,10 @@ module Reek
30
26
 
31
27
  private
32
28
 
29
+ attr_reader :configuration, :smell_category, :smell_details
30
+ attr_writer :failure_message, :failure_message_when_negated
31
+ attr_accessor :examiner
32
+
33
33
  def set_failure_messages
34
34
  # We set the failure messages for non-matching smell type unconditionally since we
35
35
  # need that in any case for "failure_message_when_negated" below.
@@ -32,7 +32,7 @@ module Reek
32
32
 
33
33
  private
34
34
 
35
- private_attr_accessor :warnings
35
+ attr_accessor :warnings
36
36
  end
37
37
  end
38
38
  end
@@ -6,7 +6,7 @@ module Reek
6
6
  class SmellMatcher
7
7
  attr_reader :smell_warning
8
8
 
9
- COMPARABLE_ATTRIBUTES = %i(message lines context source)
9
+ COMPARABLE_ATTRIBUTES = %i(message lines context source).freeze
10
10
 
11
11
  def initialize(smell_warning)
12
12
  @smell_warning = smell_warning
@@ -48,6 +48,6 @@ module Reek
48
48
 
49
49
  private
50
50
 
51
- private_attr_reader :klass_map
51
+ attr_reader :klass_map
52
52
  end
53
53
  end
@@ -6,6 +6,6 @@ module Reek
6
6
  # @public
7
7
  module Version
8
8
  # @public
9
- STRING = '3.10.1'
9
+ STRING = '3.10.2'.freeze
10
10
  end
11
11
  end
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
23
23
  s.summary = 'Code smell detector for Ruby'
24
24
 
25
25
  s.add_runtime_dependency 'codeclimate-engine-rb', '~> 0.3.1'
26
- s.add_runtime_dependency 'parser', '~> 2.3'
27
- s.add_runtime_dependency 'private_attr', '~> 1.1'
26
+ s.add_runtime_dependency 'parser', '~> 2.3', '>= 2.3.0.6'
28
27
  s.add_runtime_dependency 'rainbow', '~> 2.0'
29
28
  end
@@ -0,0 +1,31 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/cli/application'
3
+
4
+ RSpec.describe Reek::CLI::Application do
5
+ describe '#initialize' do
6
+ it 'exits with default error code on invalid options' do
7
+ call = lambda do
8
+ Reek::CLI::Silencer.silently do
9
+ Reek::CLI::Application.new ['--foo']
10
+ end
11
+ end
12
+ expect(call).to raise_error(SystemExit) do |error|
13
+ expect(error.status).to eq Reek::CLI::Options::DEFAULT_ERROR_EXIT_CODE
14
+ end
15
+ end
16
+ end
17
+
18
+ describe '#execute' do
19
+ let(:command) { double 'reek_command' }
20
+ let(:app) { Reek::CLI::Application.new [] }
21
+
22
+ before do
23
+ allow(Reek::CLI::ReekCommand).to receive(:new).and_return command
24
+ end
25
+
26
+ it "returns the command's result code" do
27
+ allow(command).to receive(:execute).and_return 'foo'
28
+ expect(app.execute).to eq 'foo'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/cli/reek_command'
3
+ require_lib 'reek/cli/options'
4
+ require_lib 'reek/cli/option_interpreter'
5
+
6
+ RSpec.describe Reek::CLI::ReekCommand do
7
+ describe '#execute' do
8
+ let(:options) { Reek::CLI::Options.new [] }
9
+ let(:option_interpreter) { Reek::CLI::OptionInterpreter.new(options) }
10
+
11
+ let(:reporter) { double 'reporter' }
12
+ let(:app) { double 'app' }
13
+
14
+ let(:command) { described_class.new option_interpreter }
15
+
16
+ before do
17
+ allow(option_interpreter).to receive(:reporter).and_return reporter
18
+ allow(reporter).to receive(:show)
19
+ end
20
+
21
+ context 'when no smells are found' do
22
+ before do
23
+ allow(option_interpreter).to receive(:sources).and_return []
24
+ allow(reporter).to receive(:smells?).and_return false
25
+ end
26
+
27
+ it 'returns a success code' do
28
+ result = command.execute app
29
+ expect(result).to eq Reek::CLI::Options::DEFAULT_SUCCESS_EXIT_CODE
30
+ end
31
+ end
32
+
33
+ context 'when smells are found' do
34
+ before do
35
+ allow(option_interpreter).to receive(:sources).and_return []
36
+ allow(reporter).to receive(:smells?).and_return true
37
+ end
38
+
39
+ it 'returns a failure code' do
40
+ result = command.execute app
41
+ expect(result).to eq Reek::CLI::Options::DEFAULT_FAILURE_EXIT_CODE
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ require_relative '../../spec_helper'
2
+ require_lib 'reek/rake/task'
3
+
4
+ RSpec.describe Reek::Rake::Task do
5
+ describe '#source_files' do
6
+ it 'is set to "lib/**/*.rb" by default' do
7
+ task = Reek::Rake::Task.new
8
+ expect(task.source_files).to eq FileList['lib/**/*.rb']
9
+ end
10
+ end
11
+
12
+ describe '#source_files=' do
13
+ it 'sets source_files to a FileList when passed a string' do
14
+ task = Reek::Rake::Task.new
15
+ task.source_files = '*.rb'
16
+ expect(task.source_files).to eq FileList['*.rb']
17
+ end
18
+ end
19
+
20
+ # SMELL: Testing a private method
21
+ describe '#command' do
22
+ let(:task) { Reek::Rake::Task.new }
23
+
24
+ it 'does not include a config file by default' do
25
+ expect(task.send(:command)).not_to include '-c'
26
+ end
27
+
28
+ it 'includes a config file when set' do
29
+ task.config_file = 'foo.reek'
30
+ expect(task.send(:command)[1..2]).to eq ['-c', 'foo.reek']
31
+ end
32
+ end
33
+ end
@@ -172,7 +172,7 @@ RSpec.describe Reek::Smells::UtilityFunction do
172
172
  expect('def simple(arga=local) arga.to_s end').not_to reek_of(:UtilityFunction)
173
173
  end
174
174
 
175
- it 'should count usages of self'do
175
+ it 'should count usages of self' do
176
176
  expect('def <=>(other) Options[:sort_order].compare(self, other) end').
177
177
  not_to reek_of(:UtilityFunction)
178
178
  end
@@ -23,9 +23,10 @@ SAMPLES_PATH = Pathname.new("#{__dir__}/samples").relative_path_from(Pathname.pw
23
23
  # Simple helpers for our specs.
24
24
  module Helpers
25
25
  def test_configuration_for(config)
26
- if config.is_a? Pathname
26
+ case config
27
+ when Pathname
27
28
  configuration = Reek::Configuration::AppConfiguration.from_path(config)
28
- elsif config.is_a? Hash
29
+ when Hash
29
30
  configuration = Reek::Configuration::AppConfiguration.from_map default_directive: config
30
31
  else
31
32
  raise "Unknown config given in `test_configuration_for`: #{config.inspect}"
@@ -4,7 +4,7 @@ require 'yaml'
4
4
  namespace :configuration do
5
5
  desc 'Updates the default configuration file when smell defaults change'
6
6
  task :update_default_configuration do
7
- DEFAULT_SMELL_CONFIGURATION = 'defaults.reek'
7
+ DEFAULT_SMELL_CONFIGURATION = 'defaults.reek'.freeze
8
8
  content = Reek::Smells::SmellRepository.smell_types.each_with_object({}) do |klass, hash|
9
9
  hash[klass.smell_type] = klass.default_config
10
10
  end
@@ -4,7 +4,7 @@ require 'rspec/core/rake_task'
4
4
  namespace 'test' do
5
5
  RSpec::Core::RakeTask.new('spec') do |t|
6
6
  t.pattern = 'spec/reek/**/*_spec.rb'
7
- t.ruby_opts = ['-Ilib -w']
7
+ t.ruby_opts = ['-rsimplecov -Ilib -w']
8
8
  end
9
9
 
10
10
  desc 'Tests code quality'