reek 4.0.1 → 4.0.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +3 -3
  4. data/README.md +1 -1
  5. data/docs/Attribute.md +1 -1
  6. data/docs/Boolean-Parameter.md +8 -6
  7. data/docs/Class-Variable.md +1 -1
  8. data/docs/Command-Line-Options.md +26 -1
  9. data/docs/Control-Couple.md +13 -9
  10. data/docs/Control-Parameter.md +8 -5
  11. data/docs/Data-Clump.md +9 -7
  12. data/docs/Duplicate-Method-Call.md +1 -1
  13. data/docs/Irresponsible-Module.md +4 -3
  14. data/docs/Large-Class.md +7 -12
  15. data/docs/Long-Parameter-List.md +7 -6
  16. data/docs/Long-Yield-List.md +7 -6
  17. data/docs/Nested-Iterators.md +7 -7
  18. data/docs/Nil-Check.md +8 -4
  19. data/docs/Prima-Donna-Method.md +19 -9
  20. data/docs/Reek-Driven-Development.md +3 -1
  21. data/docs/Repeated-Conditional.md +5 -2
  22. data/docs/Too-Many-Instance-Variables.md +7 -7
  23. data/docs/Too-Many-Methods.md +10 -9
  24. data/docs/Too-Many-Statements.md +8 -4
  25. data/docs/Uncommunicative-Method-Name.md +8 -7
  26. data/docs/Uncommunicative-Module-Name.md +8 -8
  27. data/docs/Uncommunicative-Name.md +5 -3
  28. data/docs/Uncommunicative-Parameter-Name.md +9 -9
  29. data/docs/Uncommunicative-Variable-Name.md +8 -7
  30. data/docs/Unused-Parameters.md +5 -4
  31. data/docs/Unused-Private-Method.md +3 -3
  32. data/docs/Utility-Function.md +3 -3
  33. data/lib/reek/ast/object_refs.rb +4 -4
  34. data/lib/reek/cli/application.rb +40 -4
  35. data/lib/reek/cli/command/base_command.rb +3 -2
  36. data/lib/reek/cli/command/report_command.rb +35 -5
  37. data/lib/reek/cli/command/todo_list_command.rb +4 -4
  38. data/lib/reek/configuration/app_configuration.rb +15 -15
  39. data/lib/reek/configuration/default_directive.rb +2 -1
  40. data/lib/reek/context/code_context.rb +1 -1
  41. data/lib/reek/context/module_context.rb +5 -6
  42. data/lib/reek/spec.rb +6 -4
  43. data/lib/reek/spec/should_reek_of.rb +11 -3
  44. data/lib/reek/version.rb +1 -1
  45. data/spec/factories/factories.rb +0 -11
  46. data/spec/reek/cli/application_spec.rb +83 -9
  47. data/spec/reek/cli/command/report_command_spec.rb +17 -14
  48. data/spec/reek/cli/command/todo_list_command_spec.rb +12 -10
  49. data/spec/reek/smells/duplicate_method_call_spec.rb +8 -14
  50. data/spec/reek/smells/nested_iterators_spec.rb +12 -16
  51. data/spec/reek/smells/uncommunicative_method_name_spec.rb +2 -4
  52. data/spec/reek/smells/uncommunicative_module_name_spec.rb +2 -4
  53. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +2 -4
  54. data/spec/reek/smells/unused_private_method_spec.rb +47 -48
  55. data/spec/reek/smells/utility_function_spec.rb +5 -10
  56. data/spec/reek/spec/should_reek_of_spec.rb +27 -0
  57. metadata +2 -6
  58. data/lib/reek/cli/input.rb +0 -49
  59. data/lib/reek/cli/option_interpreter.rb +0 -58
  60. data/spec/reek/cli/input_spec.rb +0 -71
  61. data/spec/reek/cli/option_interpreter_spec.rb +0 -20
@@ -6,14 +6,15 @@ module Reek
6
6
  # Base class for all commands
7
7
  #
8
8
  class BaseCommand
9
- def initialize(options, sources:)
9
+ def initialize(options:, sources:, configuration:)
10
10
  @options = options
11
11
  @sources = sources
12
+ @configuration = configuration
12
13
  end
13
14
 
14
15
  private
15
16
 
16
- attr_reader :options, :sources
17
+ attr_reader :options, :sources, :configuration
17
18
 
18
19
  def smell_names
19
20
  @smell_names ||= options.smells_to_detect
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'base_command'
3
3
  require_relative '../../examiner'
4
+ require_relative '../../report'
4
5
 
5
6
  module Reek
6
7
  module CLI
@@ -10,19 +11,19 @@ module Reek
10
11
  # text report format.
11
12
  #
12
13
  class ReportCommand < BaseCommand
13
- def execute(app)
14
- populate_reporter_with_smells app
14
+ def execute
15
+ populate_reporter_with_smells
15
16
  reporter.show
16
17
  result_code
17
18
  end
18
19
 
19
20
  private
20
21
 
21
- def populate_reporter_with_smells(app)
22
+ def populate_reporter_with_smells
22
23
  sources.each do |source|
23
24
  reporter.add_examiner Examiner.new(source,
24
25
  filter_by_smells: smell_names,
25
- configuration: app.configuration)
26
+ configuration: configuration)
26
27
  end
27
28
  end
28
29
 
@@ -31,7 +32,36 @@ module Reek
31
32
  end
32
33
 
33
34
  def reporter
34
- @reporter ||= options.reporter
35
+ @reporter ||=
36
+ report_class.new(
37
+ warning_formatter: warning_formatter,
38
+ report_formatter: Report::Formatter,
39
+ sort_by_issue_count: sort_by_issue_count,
40
+ heading_formatter: heading_formatter)
41
+ end
42
+
43
+ def report_class
44
+ Report.report_class(options.report_format)
45
+ end
46
+
47
+ def warning_formatter
48
+ warning_formatter_class.new(location_formatter: location_formatter)
49
+ end
50
+
51
+ def warning_formatter_class
52
+ Report.warning_formatter_class(options.show_links ? :wiki_links : :simple)
53
+ end
54
+
55
+ def location_formatter
56
+ Report.location_formatter(options.location_format)
57
+ end
58
+
59
+ def heading_formatter
60
+ Report.heading_formatter(options.show_empty ? :verbose : :quiet)
61
+ end
62
+
63
+ def sort_by_issue_count
64
+ options.sorting == :smelliness
35
65
  end
36
66
  end
37
67
  end
@@ -12,8 +12,8 @@ module Reek
12
12
  class TodoListCommand < BaseCommand
13
13
  FILE_NAME = '.todo.reek'.freeze
14
14
 
15
- def execute(app)
16
- smells = scan_for_smells(app)
15
+ def execute
16
+ smells = scan_for_smells
17
17
  if smells.empty?
18
18
  puts "\n'.todo.reek' not generated because "\
19
19
  'there were no smells found!'
@@ -27,11 +27,11 @@ module Reek
27
27
 
28
28
  private
29
29
 
30
- def scan_for_smells(app)
30
+ def scan_for_smells
31
31
  sources.map do |source|
32
32
  Examiner.new(source,
33
33
  filter_by_smells: smell_names,
34
- configuration: app.configuration)
34
+ configuration: configuration)
35
35
  end.map(&:smells).flatten
36
36
  end
37
37
 
@@ -70,6 +70,19 @@ module Reek
70
70
  excluded_paths.include?(path)
71
71
  end
72
72
 
73
+ def load_values(configuration_hash)
74
+ configuration_hash.each do |key, value|
75
+ case
76
+ when key == EXCLUDE_PATHS_KEY
77
+ excluded_paths.add value
78
+ when smell_type?(key)
79
+ default_directive.add key, value
80
+ else
81
+ directory_directives.add key, value
82
+ end
83
+ end
84
+ end
85
+
73
86
  private
74
87
 
75
88
  attr_writer :directory_directives, :default_directive, :excluded_paths
@@ -87,22 +100,9 @@ module Reek
87
100
  end
88
101
 
89
102
  def find_and_load(path: nil)
90
- configuration_file = ConfigurationFileFinder.find_and_load(path: path)
103
+ configuration_hash = ConfigurationFileFinder.find_and_load(path: path)
91
104
 
92
- load_values(configuration_file)
93
- end
94
-
95
- def load_values(configuration_file)
96
- configuration_file.each do |key, value|
97
- case
98
- when key == EXCLUDE_PATHS_KEY
99
- excluded_paths.add value
100
- when smell_type?(key)
101
- default_directive.add key, value
102
- else
103
- directory_directives.add key, value
104
- end
105
- end
105
+ load_values(configuration_hash)
106
106
  end
107
107
  end
108
108
  end
@@ -8,7 +8,8 @@ module Reek
8
8
  include ConfigurationValidator
9
9
 
10
10
  def add(key, config)
11
- self[key_to_smell_detector(key)] = config
11
+ detector = key_to_smell_detector(key)
12
+ self[detector] = (self[detector] || {}).merge config
12
13
  end
13
14
  end
14
15
  end
@@ -24,7 +24,7 @@ module Reek
24
24
 
25
25
  # Initializes a new CodeContext.
26
26
  #
27
- # @param parent [CodeContext, nil] The parent context
27
+ # @param _parent [CodeContext, nil] The parent context
28
28
  # @param exp [Reek::AST::Node] The code described by this context
29
29
  #
30
30
  # For example, given the following code:
@@ -45,16 +45,15 @@ module Reek
45
45
  end
46
46
 
47
47
  def defined_instance_methods(visibility: :public)
48
- each.select do |context|
49
- context.is_a?(Context::MethodContext) &&
50
- context.visibility == visibility
48
+ instance_method_children.select do |context|
49
+ context.visibility == visibility
51
50
  end
52
51
  end
53
52
 
54
53
  def instance_method_calls
55
- each.
56
- grep(SendContext).
57
- select { |context| context.parent.class == MethodContext }
54
+ instance_method_children.flat_map do |context|
55
+ context.children.grep(SendContext)
56
+ end
58
57
  end
59
58
 
60
59
  #
data/lib/reek/spec.rb CHANGED
@@ -16,9 +16,9 @@ module Reek
16
16
  # Here's a spec that ensures there are no active code smells in the current project:
17
17
  #
18
18
  # describe 'source code quality' do
19
- # Dir['lib/**/*.rb'].each do |path|
19
+ # Pathname.glob('lib/**/*.rb').each do |path|
20
20
  # it "reports no smells in #{path}" do
21
- # File.new(path).should_not reek
21
+ # expect(path).not_to reek
22
22
  # end
23
23
  # end
24
24
  # end
@@ -26,7 +26,9 @@ module Reek
26
26
  # And here's an even simpler way to do the same:
27
27
  #
28
28
  # it 'has no code smells' do
29
- # Dir['lib/**/*.rb'].should_not reek
29
+ # Pathname.glob('lib/**/*.rb').each do |path|
30
+ # expect(path).not_to reek
31
+ # end
30
32
  # end
31
33
  #
32
34
  # Here's a simple check of a code fragment:
@@ -63,7 +65,7 @@ module Reek
63
65
  # much sense.
64
66
  #
65
67
  # @param smell_type [Symbol, String, Class] The "smell type" to check for.
66
- # @param smells_details [Hash] A hash containing "smell warning" parameters
68
+ # @param smell_details [Hash] A hash containing "smell warning" parameters
67
69
  #
68
70
  # @example Without smell_details
69
71
  #
@@ -15,14 +15,22 @@ module Reek
15
15
  smell_details = {},
16
16
  configuration = Configuration::AppConfiguration.default)
17
17
  @smell_type = normalize smell_type_or_class
18
- @smell_details = smell_details
19
- @configuration = configuration
18
+ @smell_details = smell_details
19
+ @configuration = configuration
20
+ @configuration.load_values(smell_type => { Smells::SmellConfiguration::ENABLED_KEY => true })
20
21
  end
21
22
 
22
23
  def matches?(source)
24
+ @matching_smell_types = nil
23
25
  self.examiner = Examiner.new(source, configuration: configuration)
24
26
  set_failure_messages
25
- matching_smell_types? && matching_smell_details?
27
+ matching_smell_details?
28
+ end
29
+
30
+ def with_config(config_hash)
31
+ new_configuration = Configuration::AppConfiguration.default
32
+ new_configuration.load_values(smell_type => config_hash)
33
+ self.class.new(smell_type, smell_details, new_configuration)
26
34
  end
27
35
 
28
36
  private
data/lib/reek/version.rb CHANGED
@@ -7,6 +7,6 @@ module Reek
7
7
  # @public
8
8
  module Version
9
9
  # @public
10
- STRING = '4.0.1'.freeze
10
+ STRING = '4.0.2'.freeze
11
11
  end
12
12
  end
@@ -1,7 +1,6 @@
1
1
  require_relative '../../lib/reek/smells'
2
2
  require_relative '../../lib/reek/smells/smell_detector'
3
3
  require_relative '../../lib/reek/smells/smell_warning'
4
- require_relative '../../lib/reek/cli/option_interpreter'
5
4
  require_relative '../../lib/reek/cli/options'
6
5
 
7
6
  FactoryGirl.define do
@@ -53,14 +52,4 @@ FactoryGirl.define do
53
52
  parameters: parameters)
54
53
  end
55
54
  end
56
-
57
- factory :options_interpreter_with_empty_sources, class: Reek::CLI::OptionInterpreter do
58
- transient do
59
- options { Reek::CLI::Options.new [] }
60
- end
61
-
62
- initialize_with do
63
- Reek::CLI::OptionInterpreter.new(options)
64
- end
65
- end
66
55
  end
@@ -15,18 +15,92 @@ RSpec.describe Reek::CLI::Application do
15
15
  end
16
16
  end
17
17
 
18
- context 'report_command' do
19
- describe '#execute' do
20
- let(:command) { double 'reek_command' }
21
- let(:app) { Reek::CLI::Application.new [] }
18
+ let(:path_excluded_in_configuration) do
19
+ SAMPLES_PATH.join('source_with_exclude_paths/ignore_me/uncommunicative_method_name.rb')
20
+ end
21
+
22
+ let(:configuration) { test_configuration_for(SAMPLES_PATH.join('configuration/with_excluded_paths.reek')) }
23
+
24
+ describe '#execute' do
25
+ let(:command) { double 'reek_command' }
26
+ let(:app) { Reek::CLI::Application.new [] }
27
+
28
+ before do
29
+ allow(Reek::CLI::Command::ReportCommand).to receive(:new).and_return command
30
+ allow(command).to receive(:execute).and_return 'foo'
31
+ end
32
+
33
+ it "returns the command's result code" do
34
+ expect(app.execute).to eq 'foo'
35
+ end
22
36
 
23
- before do
24
- allow(Reek::CLI::Command::ReportCommand).to receive(:new).and_return command
37
+ context 'when no source files given' do
38
+ context 'and input was piped' do
39
+ before do
40
+ allow_any_instance_of(IO).to receive(:tty?).and_return(false)
41
+ end
42
+
43
+ it 'should use source form pipe' do
44
+ app.execute
45
+ expect(Reek::CLI::Command::ReportCommand).to have_received(:new).
46
+ with(sources: [$stdin],
47
+ configuration: Reek::Configuration::AppConfiguration,
48
+ options: Reek::CLI::Options)
49
+ end
25
50
  end
26
51
 
27
- it "returns the command's result code" do
28
- allow(command).to receive(:execute).and_return 'foo'
29
- expect(app.execute).to eq 'foo'
52
+ context 'and input was not piped' do
53
+ before do
54
+ allow_any_instance_of(IO).to receive(:tty?).and_return(true)
55
+ end
56
+
57
+ it 'should use working directory as source' do
58
+ expected_sources = Reek::Source::SourceLocator.new(['.']).sources
59
+ app.execute
60
+ expect(Reek::CLI::Command::ReportCommand).to have_received(:new).
61
+ with(sources: expected_sources,
62
+ configuration: Reek::Configuration::AppConfiguration,
63
+ options: Reek::CLI::Options)
64
+ end
65
+ end
66
+
67
+ context 'when source files are excluded through configuration' do
68
+ let(:app) { Reek::CLI::Application.new ['--config', 'some_file.reek'] }
69
+
70
+ before do
71
+ allow(File).to receive(:exist?).and_call_original
72
+ allow(File).to receive(:exist?).with('some_file.reek').and_return true
73
+ allow(Reek::Configuration::AppConfiguration).
74
+ to receive(:from_path).
75
+ with(Pathname.new('some_file.reek')).
76
+ and_return configuration
77
+ end
78
+
79
+ it 'should use configuration for excluded paths' do
80
+ expected_sources = Reek::Source::SourceLocator.
81
+ new(['.'], configuration: configuration).sources
82
+ expect(expected_sources).not_to include(path_excluded_in_configuration)
83
+
84
+ app.execute
85
+
86
+ expect(Reek::CLI::Command::ReportCommand).to have_received(:new).
87
+ with(sources: expected_sources,
88
+ configuration: configuration,
89
+ options: Reek::CLI::Options)
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'when source files given' do
95
+ let(:app) { Reek::CLI::Application.new ['.'] }
96
+
97
+ it 'should use sources from argv' do
98
+ expected_sources = Reek::Source::SourceLocator.new(['.']).sources
99
+ app.execute
100
+ expect(Reek::CLI::Command::ReportCommand).to have_received(:new).
101
+ with(sources: expected_sources,
102
+ configuration: Reek::Configuration::AppConfiguration,
103
+ options: Reek::CLI::Options)
30
104
  end
31
105
  end
32
106
  end
@@ -1,41 +1,44 @@
1
1
  require_relative '../../../spec_helper'
2
2
  require_lib 'reek/cli/command/report_command'
3
3
  require_lib 'reek/cli/options'
4
- require_lib 'reek/cli/option_interpreter'
5
4
 
6
5
  RSpec.describe Reek::CLI::Command::ReportCommand do
7
6
  describe '#execute' do
8
7
  let(:options) { Reek::CLI::Options.new [] }
9
- let(:option_interpreter) { Reek::CLI::OptionInterpreter.new(options) }
10
8
 
11
9
  let(:reporter) { double 'reporter' }
12
- let(:app) { double 'app' }
10
+ let(:configuration) { double 'configuration' }
11
+ let(:sources) { [source_file] }
13
12
 
14
- let(:command) { described_class.new(option_interpreter, sources: []) }
13
+ let(:clean_file) { Pathname.glob("#{SAMPLES_PATH}/three_clean_files/*.rb").first }
14
+ let(:smelly_file) { Pathname.glob("#{SAMPLES_PATH}/two_smelly_files/*.rb").first }
15
+
16
+ let(:command) do
17
+ described_class.new(options: options,
18
+ sources: sources,
19
+ configuration: configuration)
20
+ end
15
21
 
16
22
  before do
17
- allow(option_interpreter).to receive(:reporter).and_return reporter
18
- allow(reporter).to receive(:show)
23
+ allow(configuration).to receive(:directive_for).and_return({})
19
24
  end
20
25
 
21
26
  context 'when no smells are found' do
22
- before do
23
- allow(reporter).to receive(:smells?).and_return false
24
- end
27
+ let(:source_file) { clean_file }
25
28
 
26
29
  it 'returns a success code' do
27
- result = command.execute app
30
+ result = command.execute
28
31
  expect(result).to eq Reek::CLI::Options::DEFAULT_SUCCESS_EXIT_CODE
29
32
  end
30
33
  end
31
34
 
32
35
  context 'when smells are found' do
33
- before do
34
- allow(reporter).to receive(:smells?).and_return true
35
- end
36
+ let(:source_file) { smelly_file }
36
37
 
37
38
  it 'returns a failure code' do
38
- result = command.execute app
39
+ result = Reek::CLI::Silencer.silently do
40
+ command.execute
41
+ end
39
42
  expect(result).to eq Reek::CLI::Options::DEFAULT_FAILURE_EXIT_CODE
40
43
  end
41
44
  end