reek 3.10.1 → 3.10.2

Sign up to get free protection for your applications and to get access to all the features.
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'