reek 4.5.0 → 4.6.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -7
- data/.travis.yml +13 -9
- data/CHANGELOG.md +36 -3
- data/Dockerfile +6 -5
- data/Gemfile +8 -7
- data/README.md +45 -27
- data/docs/How-To-Write-New-Detectors.md +6 -6
- data/docs/Irresponsible-Module.md +8 -1
- data/docs/Nil-Check.md +1 -1
- data/docs/Prima-Donna-Method.md +27 -0
- data/docs/RSpec-matchers.md +5 -10
- data/docs/Unused-Private-Method.md +27 -0
- data/docs/templates/default/docstring/setup.rb +1 -8
- data/features/command_line_interface/options.feature +5 -1
- data/features/command_line_interface/stdin.feature +1 -1
- data/features/configuration_files/exclude_paths_directives.feature +43 -0
- data/features/configuration_files/warn_about_multiple_configuration_files.feature +44 -0
- data/features/configuration_via_source_comments/erroneous_source_comments.feature +15 -0
- data/features/samples.feature +3 -8
- data/features/step_definitions/reek_steps.rb +5 -5
- data/features/todo_list.feature +1 -2
- data/lib/reek/ast/reference_collector.rb +2 -4
- data/lib/reek/ast/sexp_extensions/arguments.rb +0 -5
- data/lib/reek/ast/sexp_extensions/constant.rb +1 -0
- data/lib/reek/cli/application.rb +7 -8
- data/lib/reek/cli/command/report_command.rb +3 -1
- data/lib/reek/cli/options.rb +29 -13
- data/lib/reek/cli/status.rb +10 -0
- data/lib/reek/code_comment.rb +48 -6
- data/lib/reek/configuration/configuration_file_finder.rb +87 -27
- data/lib/reek/context/ghost_context.rb +1 -2
- data/lib/reek/context_builder.rb +26 -1
- data/lib/reek/detector_repository.rb +64 -0
- data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +37 -0
- data/lib/reek/errors/bad_detector_in_comment_error.rb +2 -1
- data/lib/reek/errors/base_error.rb +9 -0
- data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +2 -1
- data/lib/reek/errors/incomprehensible_source_error.rb +47 -0
- data/lib/reek/errors/parse_error.rb +19 -0
- data/lib/reek/examiner.rb +17 -41
- data/lib/reek/logging_error_handler.rb +15 -0
- data/lib/reek/report/code_climate/code_climate_configuration.rb +12 -0
- data/lib/reek/report/code_climate/code_climate_configuration.yml +156 -0
- data/lib/reek/report/code_climate/code_climate_fingerprint.rb +4 -2
- data/lib/reek/report/code_climate/code_climate_formatter.rb +2 -2
- data/lib/reek/smell_configuration.rb +64 -0
- data/lib/reek/smell_detectors/attribute.rb +0 -2
- data/lib/reek/smell_detectors/base_detector.rb +24 -4
- data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
- data/lib/reek/smell_detectors/class_variable.rb +0 -1
- data/lib/reek/smell_detectors/control_parameter.rb +0 -1
- data/lib/reek/smell_detectors/data_clump.rb +0 -1
- data/lib/reek/smell_detectors/duplicate_method_call.rb +0 -1
- data/lib/reek/smell_detectors/feature_envy.rb +0 -1
- data/lib/reek/smell_detectors/instance_variable_assumption.rb +0 -1
- data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
- data/lib/reek/smell_detectors/long_parameter_list.rb +0 -2
- data/lib/reek/smell_detectors/long_yield_list.rb +0 -1
- data/lib/reek/smell_detectors/manual_dispatch.rb +4 -6
- data/lib/reek/smell_detectors/module_initialize.rb +5 -8
- data/lib/reek/smell_detectors/nested_iterators.rb +0 -1
- data/lib/reek/smell_detectors/nil_check.rb +0 -1
- data/lib/reek/smell_detectors/prima_donna_method.rb +30 -1
- data/lib/reek/smell_detectors/repeated_conditional.rb +0 -1
- data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
- data/lib/reek/smell_detectors/too_many_constants.rb +0 -1
- data/lib/reek/smell_detectors/too_many_instance_variables.rb +0 -1
- data/lib/reek/smell_detectors/too_many_methods.rb +0 -1
- data/lib/reek/smell_detectors/too_many_statements.rb +0 -1
- data/lib/reek/smell_detectors/uncommunicative_method_name.rb +0 -1
- data/lib/reek/smell_detectors/uncommunicative_module_name.rb +0 -1
- data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +0 -1
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +0 -1
- data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
- data/lib/reek/smell_detectors/unused_private_method.rb +0 -2
- data/lib/reek/smell_detectors/utility_function.rb +0 -1
- data/lib/reek/smell_warning.rb +85 -0
- data/lib/reek/source/source_code.rb +2 -1
- data/lib/reek/source/source_locator.rb +15 -3
- data/lib/reek/spec/should_reek_of.rb +1 -1
- data/lib/reek/spec.rb +6 -4
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +1 -1
- data/samples/configuration/more_than_one_configuration_file/todo.reek +0 -0
- data/samples/configuration/single_configuration_file/.reek +0 -0
- data/spec/factories/factories.rb +2 -10
- data/spec/quality/reek_source_spec.rb +5 -3
- data/spec/reek/ast/reference_collector_spec.rb +0 -17
- data/spec/reek/cli/application_spec.rb +25 -1
- data/spec/reek/cli/command/report_command_spec.rb +2 -2
- data/spec/reek/cli/command/todo_list_command_spec.rb +14 -71
- data/spec/reek/cli/options_spec.rb +4 -0
- data/spec/reek/code_comment_spec.rb +47 -0
- data/spec/reek/configuration/configuration_file_finder_spec.rb +38 -15
- data/spec/reek/context/code_context_spec.rb +10 -10
- data/spec/reek/context/method_context_spec.rb +1 -1
- data/spec/reek/context/module_context_spec.rb +8 -4
- data/spec/reek/{smell_detectors/detector_repository_spec.rb → detector_repository_spec.rb} +3 -3
- data/spec/reek/examiner_spec.rb +39 -40
- data/spec/reek/logging_error_handler_spec.rb +24 -0
- data/spec/reek/report/code_climate/code_climate_configuration_spec.rb +24 -0
- data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +9 -9
- data/spec/reek/report/yaml_report_spec.rb +4 -4
- data/spec/reek/{smell_detectors/smell_configuration_spec.rb → smell_configuration_spec.rb} +3 -3
- data/spec/reek/smell_detectors/base_detector_spec.rb +18 -0
- data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +2 -2
- data/spec/reek/smell_detectors/feature_envy_spec.rb +18 -8
- data/spec/reek/smell_detectors/manual_dispatch_spec.rb +13 -0
- data/spec/reek/smell_detectors/module_initialize_spec.rb +23 -2
- data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
- data/spec/reek/smell_detectors/prima_donna_method_spec.rb +12 -0
- data/spec/reek/smell_detectors/subclassed_from_core_class_spec.rb +0 -5
- data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +2 -2
- data/spec/reek/smell_detectors/unused_parameters_spec.rb +1 -1
- data/spec/reek/{smell_detectors/smell_warning_spec.rb → smell_warning_spec.rb} +9 -9
- data/spec/reek/source/source_code_spec.rb +6 -29
- data/spec/reek/source/source_locator_spec.rb +48 -16
- data/spec/reek/spec/should_reek_of_spec.rb +1 -1
- data/spec/reek/spec/should_reek_only_of_spec.rb +4 -4
- data/spec/spec_helper.rb +4 -3
- data/tasks/configuration.rake +2 -2
- metadata +26 -14
- data/lib/reek/smell_detectors/detector_repository.rb +0 -66
- data/lib/reek/smell_detectors/smell_configuration.rb +0 -66
- data/lib/reek/smell_detectors/smell_warning.rb +0 -88
- data/tasks/mutant.rake +0 -14
- /data/samples/configuration/{.reek → more_than_one_configuration_file/regular.reek} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'smell_detectors'
|
|
3
|
+
require_relative 'smell_detectors/base_detector'
|
|
4
|
+
require_relative 'configuration/app_configuration'
|
|
5
|
+
|
|
6
|
+
module Reek
|
|
7
|
+
#
|
|
8
|
+
# Contains all the existing smell detectors and exposes operations on them.
|
|
9
|
+
#
|
|
10
|
+
class DetectorRepository
|
|
11
|
+
# @return [Array<Reek::SmellDetectors::BaseDetector>] All known SmellDetectors
|
|
12
|
+
# e.g. [Reek::SmellDetectors::BooleanParameter, Reek::SmellDetectors::ClassVariable].
|
|
13
|
+
def self.smell_types
|
|
14
|
+
Reek::SmellDetectors::BaseDetector.descendants.sort_by(&:name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @param filter_by_smells [Array<String>]
|
|
18
|
+
# List of smell types to filter by, e.g. "DuplicateMethodCall".
|
|
19
|
+
# More precisely it should be whatever is returned by `BaseDetector`.smell_type.
|
|
20
|
+
# This means that you can write the "DuplicateMethodCall" from above also like this:
|
|
21
|
+
# Reek::SmellDetectors::DuplicateMethodCall.smell_type
|
|
22
|
+
# if you want to make sure you do not fat-finger strings.
|
|
23
|
+
#
|
|
24
|
+
# @return [Array<Reek::SmellDetectors::BaseDetector>] All SmellDetectors that we want to filter for
|
|
25
|
+
# e.g. [Reek::SmellDetectors::Attribute].
|
|
26
|
+
def self.eligible_smell_types(filter_by_smells = [])
|
|
27
|
+
return smell_types if filter_by_smells.empty?
|
|
28
|
+
smell_types.select do |klass|
|
|
29
|
+
filter_by_smells.include? klass.smell_type
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(smell_types: self.class.smell_types,
|
|
34
|
+
configuration: {})
|
|
35
|
+
@configuration = configuration
|
|
36
|
+
@smell_types = smell_types
|
|
37
|
+
@detectors = smell_types.map { |klass| klass.new configuration_for(klass) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def examine(context)
|
|
41
|
+
smell_detectors_for(context.type).flat_map do |detector|
|
|
42
|
+
detector.run_for(context)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
attr_reader :configuration, :smell_types, :detectors
|
|
49
|
+
|
|
50
|
+
def configuration_for(klass)
|
|
51
|
+
configuration.fetch klass, {}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def smell_detectors_for(type)
|
|
55
|
+
enabled_detectors.select do |detector|
|
|
56
|
+
detector.contexts.include? type
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def enabled_detectors
|
|
61
|
+
detectors.select { |detector| detector.config.enabled? }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'base_error'
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Errors
|
|
6
|
+
# Gets raised when trying to configure a detector with an option
|
|
7
|
+
# which is unknown to it.
|
|
8
|
+
class BadDetectorConfigurationKeyInCommentError < BaseError
|
|
9
|
+
UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
|
|
10
|
+
|
|
11
|
+
Error: You are trying to configure the smell detector '%s'
|
|
12
|
+
in one of your source code comments with the unknown option %s.
|
|
13
|
+
The source is '%s' and the comment belongs to the expression starting in line %d.
|
|
14
|
+
Here's the original comment:
|
|
15
|
+
|
|
16
|
+
%s
|
|
17
|
+
|
|
18
|
+
Please see the Reek docs for:
|
|
19
|
+
* how to configure Reek via source code comments: https://github.com/troessner/reek/blob/master/docs/Smell-Suppression.md
|
|
20
|
+
* what basic options are available: https://github.com/troessner/reek/blob/master/docs/Basic-Smell-Options.md
|
|
21
|
+
* what custom options are available by checking the detector specific documentation in /docs
|
|
22
|
+
Update the offensive comment (or remove it if no longer applicable) and re-run Reek.
|
|
23
|
+
|
|
24
|
+
EOS
|
|
25
|
+
|
|
26
|
+
def initialize(detector_name:, offensive_keys:, source:, line:, original_comment:)
|
|
27
|
+
message = format(UNKNOWN_SMELL_DETECTOR_MESSAGE,
|
|
28
|
+
detector_name,
|
|
29
|
+
offensive_keys,
|
|
30
|
+
source,
|
|
31
|
+
line,
|
|
32
|
+
original_comment)
|
|
33
|
+
super message
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_relative 'base_error'
|
|
2
3
|
|
|
3
4
|
module Reek
|
|
4
5
|
module Errors
|
|
5
6
|
# Gets raised when trying to configure a detector which is unknown to us.
|
|
6
7
|
# This might happen for multiple reasons. The users might have a typo in
|
|
7
8
|
# his comment or he might use a detector that does not exist anymore.
|
|
8
|
-
class BadDetectorInCommentError <
|
|
9
|
+
class BadDetectorInCommentError < BaseError
|
|
9
10
|
UNKNOWN_SMELL_DETECTOR_MESSAGE = <<-EOS.freeze
|
|
10
11
|
|
|
11
12
|
Error: You are trying to configure an unknown smell detector '%s' in one
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require_relative 'base_error'
|
|
2
3
|
|
|
3
4
|
module Reek
|
|
4
5
|
module Errors
|
|
5
6
|
# Gets raised when trying to use a configuration for a detector
|
|
6
7
|
# that can't be parsed into a hash.
|
|
7
|
-
class GarbageDetectorConfigurationInCommentError <
|
|
8
|
+
class GarbageDetectorConfigurationInCommentError < BaseError
|
|
8
9
|
BAD_DETECTOR_CONFIGURATION_MESSAGE = <<-EOS.freeze
|
|
9
10
|
|
|
10
11
|
Error: You are trying to configure the smell detector '%s'.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'base_error'
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Errors
|
|
6
|
+
# Gets raised when Reek is unable to process the source
|
|
7
|
+
class IncomprehensibleSourceError < BaseError
|
|
8
|
+
INCOMPREHENSIBLE_SOURCE_TEMPLATE = <<-EOS.freeze
|
|
9
|
+
!!!
|
|
10
|
+
Source %s can not be processed by Reek.
|
|
11
|
+
|
|
12
|
+
This is most likely either a bug in your Reek configuration (config file or
|
|
13
|
+
source code comments) or a Reek bug.
|
|
14
|
+
|
|
15
|
+
Please double check your Reek configuration taking the original exception
|
|
16
|
+
below into account - you might have misspelled a smell detector for instance.
|
|
17
|
+
(In the future Reek will handle configuration errors more gracefully, something
|
|
18
|
+
we are working on already).
|
|
19
|
+
|
|
20
|
+
If you feel that this is not a problem with your Reek configuration but with
|
|
21
|
+
Reek itself it would be great if you could report this back to the Reek
|
|
22
|
+
team by opening up a corresponding issue at https://github.com/troessner/reek/issues.
|
|
23
|
+
|
|
24
|
+
Please make sure to include the source in question, the Reek version
|
|
25
|
+
and the original exception below.
|
|
26
|
+
|
|
27
|
+
Exception message:
|
|
28
|
+
|
|
29
|
+
%s
|
|
30
|
+
|
|
31
|
+
Original exception:
|
|
32
|
+
|
|
33
|
+
%s
|
|
34
|
+
|
|
35
|
+
!!!
|
|
36
|
+
EOS
|
|
37
|
+
|
|
38
|
+
def initialize(origin:, original_exception:)
|
|
39
|
+
message = format(INCOMPREHENSIBLE_SOURCE_TEMPLATE,
|
|
40
|
+
origin,
|
|
41
|
+
original_exception.message,
|
|
42
|
+
original_exception.backtrace.join("\n\t"))
|
|
43
|
+
super message
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'base_error'
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Errors
|
|
6
|
+
# Gets raised when Reek is unable to process the source
|
|
7
|
+
class ParseError < BaseError
|
|
8
|
+
MESSAGE_TEMPLATE = '%s: %s: %s'.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(origin:, original_exception:)
|
|
11
|
+
message = format(MESSAGE_TEMPLATE,
|
|
12
|
+
origin,
|
|
13
|
+
original_exception.class.name,
|
|
14
|
+
original_exception.message)
|
|
15
|
+
super message
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/reek/examiner.rb
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require_relative 'context_builder'
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative 'errors/
|
|
5
|
-
require_relative 'smell_detectors/detector_repository'
|
|
3
|
+
require_relative 'detector_repository'
|
|
4
|
+
require_relative 'errors/incomprehensible_source_error'
|
|
6
5
|
require_relative 'source/source_code'
|
|
7
6
|
|
|
8
7
|
module Reek
|
|
@@ -11,35 +10,13 @@ module Reek
|
|
|
11
10
|
#
|
|
12
11
|
# @public
|
|
13
12
|
class Examiner
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Please double check your Reek configuration taking the original exception
|
|
22
|
-
below into account - you might have misspelled a smell detector for instance.
|
|
23
|
-
(In the future Reek will handle configuration errors more gracefully, something
|
|
24
|
-
we are working on already).
|
|
25
|
-
|
|
26
|
-
If you feel that this is not a problem with your Reek configuration but with
|
|
27
|
-
Reek itself it would be great if you could report this back to the Reek
|
|
28
|
-
team by opening up a corresponding issue at https://github.com/troessner/reek/issues.
|
|
29
|
-
|
|
30
|
-
Please make sure to include the source in question, the Reek version
|
|
31
|
-
and the original exception below.
|
|
32
|
-
|
|
33
|
-
Exception message:
|
|
34
|
-
|
|
35
|
-
%s
|
|
36
|
-
|
|
37
|
-
Original exception:
|
|
38
|
-
|
|
39
|
-
%s
|
|
13
|
+
# Handles no errors
|
|
14
|
+
class NullHandler
|
|
15
|
+
def handle(_exception)
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
40
19
|
|
|
41
|
-
!!!
|
|
42
|
-
EOS
|
|
43
20
|
#
|
|
44
21
|
# Creates an Examiner which scans the given +source+ for code smells.
|
|
45
22
|
#
|
|
@@ -57,11 +34,13 @@ module Reek
|
|
|
57
34
|
def initialize(source,
|
|
58
35
|
filter_by_smells: [],
|
|
59
36
|
configuration: Configuration::AppConfiguration.default,
|
|
60
|
-
detector_repository_class:
|
|
37
|
+
detector_repository_class: DetectorRepository,
|
|
38
|
+
error_handler: NullHandler.new)
|
|
61
39
|
@source = Source::SourceCode.from(source)
|
|
62
40
|
@smell_types = detector_repository_class.eligible_smell_types(filter_by_smells)
|
|
63
41
|
@detector_repository = detector_repository_class.new(smell_types: @smell_types,
|
|
64
42
|
configuration: configuration.directive_for(description))
|
|
43
|
+
@error_handler = error_handler
|
|
65
44
|
end
|
|
66
45
|
|
|
67
46
|
# @return [String] origin of the source being analysed
|
|
@@ -120,15 +99,8 @@ module Reek
|
|
|
120
99
|
return [] unless syntax_tree
|
|
121
100
|
begin
|
|
122
101
|
examine_tree
|
|
123
|
-
rescue Errors::
|
|
124
|
-
|
|
125
|
-
warn exception
|
|
126
|
-
[]
|
|
127
|
-
rescue StandardError => exception
|
|
128
|
-
warn format(INCOMPREHENSIBLE_SOURCE_TEMPLATE,
|
|
129
|
-
origin,
|
|
130
|
-
exception.message,
|
|
131
|
-
exception.backtrace.join("\n\t"))
|
|
102
|
+
rescue Errors::BaseError => exception
|
|
103
|
+
raise unless @error_handler.handle exception
|
|
132
104
|
[]
|
|
133
105
|
end
|
|
134
106
|
end
|
|
@@ -141,6 +113,10 @@ module Reek
|
|
|
141
113
|
ContextBuilder.new(syntax_tree).context_tree.flat_map do |element|
|
|
142
114
|
detector_repository.examine(element)
|
|
143
115
|
end
|
|
116
|
+
rescue Errors::BaseError
|
|
117
|
+
raise
|
|
118
|
+
rescue StandardError => exception
|
|
119
|
+
raise Errors::IncomprehensibleSourceError, origin: origin, original_exception: exception
|
|
144
120
|
end
|
|
145
121
|
end
|
|
146
122
|
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'errors/bad_detector_configuration_key_in_comment_error'
|
|
3
|
+
require_relative 'errors/bad_detector_in_comment_error'
|
|
4
|
+
require_relative 'errors/garbage_detector_configuration_in_comment_error'
|
|
5
|
+
require_relative 'errors/incomprehensible_source_error'
|
|
6
|
+
|
|
7
|
+
module Reek
|
|
8
|
+
# Handles errors by logging to stderr
|
|
9
|
+
class LoggingErrorHandler
|
|
10
|
+
def handle(exception)
|
|
11
|
+
warn exception
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Reek
|
|
3
|
+
module Report
|
|
4
|
+
# loads the smell type metadata to present in Code Climate
|
|
5
|
+
module CodeClimateConfiguration
|
|
6
|
+
def self.load
|
|
7
|
+
config_file = File.expand_path('../code_climate_configuration.yml', __FILE__)
|
|
8
|
+
YAML.load_file config_file
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -222,6 +222,135 @@ FeatureEnvy:
|
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
belongs to the Item class, not the Warehouse.
|
|
225
|
+
InstanceVariableAssumption:
|
|
226
|
+
remediation_points: 350_000
|
|
227
|
+
content: |
|
|
228
|
+
Classes should not assume that instance variables are set or present outside of the current class definition.
|
|
229
|
+
|
|
230
|
+
Good:
|
|
231
|
+
|
|
232
|
+
```Ruby
|
|
233
|
+
class Foo
|
|
234
|
+
def initialize
|
|
235
|
+
@bar = :foo
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def foo?
|
|
239
|
+
@bar == :foo
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Good as well:
|
|
245
|
+
|
|
246
|
+
```Ruby
|
|
247
|
+
class Foo
|
|
248
|
+
def foo?
|
|
249
|
+
bar == :foo
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def bar
|
|
253
|
+
@bar ||= :foo
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Bad:
|
|
259
|
+
|
|
260
|
+
```Ruby
|
|
261
|
+
class Foo
|
|
262
|
+
def go_foo!
|
|
263
|
+
@bar = :foo
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def foo?
|
|
267
|
+
@bar == :foo
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Example
|
|
273
|
+
|
|
274
|
+
Running Reek on:
|
|
275
|
+
|
|
276
|
+
```Ruby
|
|
277
|
+
class Dummy
|
|
278
|
+
def test
|
|
279
|
+
@ivar
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
would report:
|
|
285
|
+
|
|
286
|
+
```Bash
|
|
287
|
+
[1]:InstanceVariableAssumption: Dummy assumes too much for instance variable @ivar [https://github.com/troessner/reek/blob/master/docs/Instance-Variable-Assumption.md]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Note that this example would trigger this smell warning as well:
|
|
291
|
+
|
|
292
|
+
```Ruby
|
|
293
|
+
class Parent
|
|
294
|
+
def initialize(omg)
|
|
295
|
+
@omg = omg
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
class Child < Parent
|
|
300
|
+
def foo
|
|
301
|
+
@omg
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The way to address the smell warning is that you should create an `attr_reader` to use `@omg` in the subclass and not access `@omg` directly like this:
|
|
307
|
+
|
|
308
|
+
```Ruby
|
|
309
|
+
class Parent
|
|
310
|
+
attr_reader :omg
|
|
311
|
+
|
|
312
|
+
def initialize(omg)
|
|
313
|
+
@omg = omg
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
class Child < Parent
|
|
318
|
+
def foo
|
|
319
|
+
omg
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Directly accessing instance variables is considered a smell because it [breaks encapsulation](http://designisrefactoring.com/2015/03/29/organizing-data-self-encapsulation/) and makes it harder to reason about code.
|
|
325
|
+
|
|
326
|
+
If you don't want to expose those methods as public API just make them private like this:
|
|
327
|
+
|
|
328
|
+
```Ruby
|
|
329
|
+
class Parent
|
|
330
|
+
def initialize(omg)
|
|
331
|
+
@omg = omg
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
private
|
|
335
|
+
attr_reader :omg
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
class Child < Parent
|
|
339
|
+
def foo
|
|
340
|
+
omg
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
## Current Support in Reek
|
|
347
|
+
|
|
348
|
+
An instance variable must:
|
|
349
|
+
|
|
350
|
+
* be set in the constructor
|
|
351
|
+
* or be accessed through a method with lazy initialization / memoization.
|
|
352
|
+
|
|
353
|
+
If not, _Instance Variable Assumption_ will be reported.
|
|
225
354
|
IrresponsibleModule:
|
|
226
355
|
remediation_points: 350_000
|
|
227
356
|
content: |
|
|
@@ -300,6 +429,33 @@ LongYieldList:
|
|
|
300
429
|
```
|
|
301
430
|
|
|
302
431
|
A common solution to this problem would be the introduction of parameter objects.
|
|
432
|
+
ManualDispatch:
|
|
433
|
+
remediation_points: 350_000
|
|
434
|
+
content: |
|
|
435
|
+
Reek reports a _Manual Dispatch_ smell if it finds source code that manually checks whether an object responds to a method before that method is called. Manual dispatch is a type of Simulated Polymorphism which leads to code that is harder to reason about, debug, and refactor.
|
|
436
|
+
|
|
437
|
+
## Example
|
|
438
|
+
|
|
439
|
+
```Ruby
|
|
440
|
+
class MyManualDispatcher
|
|
441
|
+
attr_reader :foo
|
|
442
|
+
|
|
443
|
+
def initialize(foo)
|
|
444
|
+
@foo = foo
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def call
|
|
448
|
+
foo.bar if foo.respond_to?(:bar)
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Reek would emit the following warning:
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
test.rb -- 1 warning:
|
|
457
|
+
[9]: MyManualDispatcher manually dispatches method call (ManualDispatch)
|
|
458
|
+
```
|
|
303
459
|
ModuleInitialize:
|
|
304
460
|
remediation_points: 350_000
|
|
305
461
|
content: |
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'digest'
|
|
3
|
+
|
|
2
4
|
module Reek
|
|
3
5
|
module Report
|
|
4
6
|
# Generates a string to uniquely identify a smell
|
|
@@ -14,7 +16,7 @@ module Reek
|
|
|
14
16
|
|
|
15
17
|
identify_warning
|
|
16
18
|
|
|
17
|
-
identifying_aspects.hexdigest
|
|
19
|
+
identifying_aspects.hexdigest.freeze
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
private
|
|
@@ -33,7 +35,7 @@ module Reek
|
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def parameters
|
|
36
|
-
warning.parameters.
|
|
38
|
+
warning.parameters.reject { |key, _| NON_IDENTIFYING_PARAMETERS.include?(key) }.sort.to_s
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
def warning_uniquely_identifiable?
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'codeclimate_engine'
|
|
3
|
+
require_relative 'code_climate_configuration'
|
|
3
4
|
|
|
4
5
|
module Reek
|
|
5
6
|
module Report
|
|
@@ -56,8 +57,7 @@ module Reek
|
|
|
56
57
|
|
|
57
58
|
def configuration
|
|
58
59
|
@configuration ||= begin
|
|
59
|
-
|
|
60
|
-
YAML.load_file config_file
|
|
60
|
+
CodeClimateConfiguration.load
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Reek
|
|
3
|
+
#
|
|
4
|
+
# Represents a single set of configuration options for a smell detector
|
|
5
|
+
#
|
|
6
|
+
class SmellConfiguration
|
|
7
|
+
# The name of the config field that specifies whether a smell is
|
|
8
|
+
# enabled. Set to +true+ or +false+.
|
|
9
|
+
ENABLED_KEY = 'enabled'.freeze
|
|
10
|
+
|
|
11
|
+
# The name of the config field that sets scope-specific overrides
|
|
12
|
+
# for other values in the current smell detector's configuration.
|
|
13
|
+
OVERRIDES_KEY = 'overrides'.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(hash)
|
|
16
|
+
@options = hash
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def merge(new_options)
|
|
20
|
+
options.merge!(new_options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def enabled?
|
|
24
|
+
options[ENABLED_KEY]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def overrides_for(context)
|
|
28
|
+
Overrides.new(options.fetch(OVERRIDES_KEY, {})).for_context(context)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Retrieves the value, if any, for the given +key+ in the given +context+.
|
|
32
|
+
#
|
|
33
|
+
# Raises an error if neither the context nor this config have a value for
|
|
34
|
+
# the key.
|
|
35
|
+
#
|
|
36
|
+
def value(key, context)
|
|
37
|
+
overrides_for(context).each { |conf| return conf[key] if conf.key?(key) }
|
|
38
|
+
options.fetch(key)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :options
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# A set of context-specific overrides for smell detectors.
|
|
48
|
+
#
|
|
49
|
+
class Overrides
|
|
50
|
+
def initialize(hash)
|
|
51
|
+
@hash = hash
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Find any overrides that match the supplied context
|
|
55
|
+
def for_context(context)
|
|
56
|
+
contexts = hash.keys.select { |ckey| context.matches?([ckey]) }
|
|
57
|
+
contexts.map { |exc| hash[exc] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
attr_reader :hash
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'set'
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative '../smell_warning'
|
|
4
|
+
require_relative '../smell_configuration'
|
|
4
5
|
|
|
5
6
|
module Reek
|
|
6
7
|
module SmellDetectors
|
|
@@ -14,6 +15,7 @@ module Reek
|
|
|
14
15
|
# for details.
|
|
15
16
|
#
|
|
16
17
|
# :reek:UnusedPrivateMethod: { exclude: [ smell_warning ] }
|
|
18
|
+
# :reek:TooManyMethods: { max_methods: 18 }
|
|
17
19
|
class BaseDetector
|
|
18
20
|
attr_reader :config
|
|
19
21
|
# The name of the config field that lists the names of code contexts
|
|
@@ -105,9 +107,9 @@ module Reek
|
|
|
105
107
|
# Returns all descendants of BaseDetector
|
|
106
108
|
#
|
|
107
109
|
# @return [Array<Constant>], e.g.:
|
|
108
|
-
# [Reek::
|
|
109
|
-
# Reek::
|
|
110
|
-
# Reek::
|
|
110
|
+
# [Reek::SmellDetectors::Attribute,
|
|
111
|
+
# Reek::SmellDetectors::BooleanParameter,
|
|
112
|
+
# Reek::SmellDetectors::ClassVariable,
|
|
111
113
|
# ...]
|
|
112
114
|
#
|
|
113
115
|
def descendants
|
|
@@ -122,6 +124,24 @@ module Reek
|
|
|
122
124
|
descendants.map { |descendant| descendant.to_s.split('::').last }.
|
|
123
125
|
include?(detector)
|
|
124
126
|
end
|
|
127
|
+
|
|
128
|
+
#
|
|
129
|
+
# Transform a detector name to the corresponding constant.
|
|
130
|
+
# Note that we assume a valid name - exceptions are not handled here.
|
|
131
|
+
#
|
|
132
|
+
# @param detector_name [String] the detector in question, e.g. 'DuplicateMethodCall'
|
|
133
|
+
# @return [SmellDetector] - this will return the class, not an instance
|
|
134
|
+
#
|
|
135
|
+
def to_detector(detector_name)
|
|
136
|
+
SmellDetectors.const_get detector_name
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
#
|
|
140
|
+
# @return [Set<Symbol>] - all configuration keys that are available for this detector
|
|
141
|
+
#
|
|
142
|
+
def configuration_keys
|
|
143
|
+
Set.new(default_config.keys.map(&:to_sym))
|
|
144
|
+
end
|
|
125
145
|
end
|
|
126
146
|
end
|
|
127
147
|
end
|