reek 4.7.3 → 5.0.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/.codeclimate.yml +17 -12
- data/.rubocop.yml +18 -3
- data/.simplecov +1 -0
- data/.travis.yml +3 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +45 -0
- data/Gemfile +4 -4
- data/README.md +134 -104
- data/Rakefile +16 -3
- data/bin/reek +1 -3
- data/docs/API.md +2 -9
- data/docs/Basic-Smell-Options.md +51 -11
- data/docs/Code-Smells.md +1 -1
- data/docs/Command-Line-Options.md +14 -4
- data/docs/Duplicate-Method-Call.md +49 -1
- data/docs/Feature-Envy.md +44 -0
- data/docs/How-To-Write-New-Detectors.md +2 -3
- data/docs/Instance-Variable-Assumption.md +1 -1
- data/docs/{Prima-Donna-Method.md → Missing-Safe-Method.md} +11 -9
- data/docs/Rake-Task.md +1 -1
- data/docs/Reek-4-to-Reek-5-migration.md +193 -0
- data/docs/Reek-Driven-Development.md +1 -1
- data/docs/Uncommunicative-Method-Name.md +45 -6
- data/docs/Uncommunicative-Module-Name.md +49 -7
- data/docs/Uncommunicative-Parameter-Name.md +43 -5
- data/docs/Uncommunicative-Variable-Name.md +73 -2
- data/docs/Unused-Private-Method.md +2 -2
- data/docs/defaults.reek.yml +129 -0
- data/docs/yard_plugin.rb +1 -0
- data/features/command_line_interface/basic_usage.feature +2 -2
- data/features/command_line_interface/options.feature +46 -4
- data/features/command_line_interface/show_progress.feature +4 -4
- data/features/command_line_interface/smell_selection.feature +1 -1
- data/features/command_line_interface/smells_count.feature +6 -6
- data/features/command_line_interface/stdin.feature +30 -8
- data/features/configuration_files/accept_setting.feature +45 -28
- data/features/configuration_files/directory_specific_directives.feature +78 -73
- data/features/configuration_files/exclude_directives.feature +11 -10
- data/features/configuration_files/exclude_paths_directives.feature +4 -4
- data/features/configuration_files/masking_smells.feature +38 -9
- data/features/configuration_files/mix_accept_reject_setting.feature +31 -28
- data/features/configuration_files/reject_setting.feature +52 -41
- data/features/configuration_files/schema_validation.feature +59 -0
- data/features/configuration_files/unused_private_method.feature +18 -16
- data/features/configuration_loading.feature +53 -10
- data/features/configuration_via_source_comments/erroneous_source_comments.feature +2 -2
- data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
- data/features/locales.feature +2 -2
- data/features/rake_task/rake_task.feature +15 -15
- data/features/reports/json.feature +3 -3
- data/features/reports/reports.feature +34 -34
- data/features/reports/yaml.feature +3 -3
- data/features/rspec_matcher.feature +9 -1
- data/features/samples.feature +287 -287
- data/features/step_definitions/reek_steps.rb +4 -0
- data/features/step_definitions/sample_file_steps.rb +9 -4
- data/features/support/env.rb +2 -2
- data/features/todo_list.feature +26 -23
- data/lib/reek/ast/node.rb +40 -55
- data/lib/reek/ast/object_refs.rb +1 -1
- data/lib/reek/ast/reference_collector.rb +2 -4
- data/lib/reek/ast/sexp_extensions/case.rb +1 -1
- data/lib/reek/ast/sexp_extensions/if.rb +8 -1
- data/lib/reek/ast/sexp_extensions/logical_operators.rb +1 -1
- data/lib/reek/ast/sexp_extensions/methods.rb +4 -6
- data/lib/reek/cli/application.rb +4 -3
- data/lib/reek/cli/command/report_command.rb +1 -2
- data/lib/reek/cli/command/todo_list_command.rb +8 -8
- data/lib/reek/cli/options.rb +29 -14
- data/lib/reek/cli/silencer.rb +14 -3
- data/lib/reek/code_comment.rb +14 -16
- data/lib/reek/configuration/app_configuration.rb +32 -28
- data/lib/reek/configuration/configuration_converter.rb +110 -0
- data/lib/reek/configuration/configuration_file_finder.rb +15 -40
- data/lib/reek/configuration/configuration_validator.rb +12 -23
- data/lib/reek/configuration/default_directive.rb +17 -3
- data/lib/reek/configuration/directory_directives.rb +17 -11
- data/lib/reek/configuration/excluded_paths.rb +1 -1
- data/lib/reek/configuration/rake_task_converter.rb +29 -0
- data/lib/reek/configuration/schema.yml +210 -0
- data/lib/reek/configuration/schema_validator.rb +38 -0
- data/lib/reek/context/attribute_context.rb +1 -1
- data/lib/reek/context/code_context.rb +8 -11
- data/lib/reek/context/method_context.rb +7 -12
- data/lib/reek/context/module_context.rb +4 -4
- data/lib/reek/context_builder.rb +11 -11
- data/lib/reek/detector_repository.rb +6 -0
- data/lib/reek/documentation_link.rb +28 -0
- data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +13 -12
- data/lib/reek/errors/bad_detector_in_comment_error.rb +11 -10
- data/lib/reek/errors/base_error.rb +3 -0
- data/lib/reek/errors/config_file_error.rb +11 -0
- data/lib/reek/errors/encoding_error.rb +16 -11
- data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +11 -10
- data/lib/reek/errors/incomprehensible_source_error.rb +20 -22
- data/lib/reek/errors/syntax_error.rb +41 -0
- data/lib/reek/examiner.rb +19 -25
- data/lib/reek/logging_error_handler.rb +7 -5
- data/lib/reek/rake/task.rb +3 -3
- data/lib/reek/report/base_report.rb +8 -12
- data/lib/reek/report/code_climate/code_climate_configuration.rb +1 -1
- data/lib/reek/report/code_climate/code_climate_configuration.yml +6 -10
- data/lib/reek/report/documentation_link_warning_formatter.rb +17 -0
- data/lib/reek/report/heading_formatter.rb +54 -0
- data/lib/reek/report/json_report.rb +1 -1
- data/lib/reek/report/location_formatter.rb +40 -0
- data/lib/reek/report/progress_formatter.rb +79 -0
- data/lib/reek/report/simple_warning_formatter.rb +34 -0
- data/lib/reek/report/text_report.rb +1 -2
- data/lib/reek/report/xml_report.rb +3 -3
- data/lib/reek/report/yaml_report.rb +1 -1
- data/lib/reek/report.rb +15 -10
- data/lib/reek/smell_configuration.rb +2 -2
- data/lib/reek/smell_detectors/attribute.rb +0 -1
- data/lib/reek/smell_detectors/base_detector.rb +9 -12
- data/lib/reek/smell_detectors/boolean_parameter.rb +0 -1
- data/lib/reek/smell_detectors/class_variable.rb +3 -11
- data/lib/reek/smell_detectors/control_parameter.rb +17 -32
- data/lib/reek/smell_detectors/data_clump.rb +3 -4
- data/lib/reek/smell_detectors/duplicate_method_call.rb +6 -7
- data/lib/reek/smell_detectors/feature_envy.rb +1 -1
- data/lib/reek/smell_detectors/instance_variable_assumption.rb +1 -10
- data/lib/reek/smell_detectors/irresponsible_module.rb +0 -1
- data/lib/reek/smell_detectors/long_parameter_list.rb +1 -2
- data/lib/reek/smell_detectors/long_yield_list.rb +2 -3
- data/lib/reek/smell_detectors/manual_dispatch.rb +3 -3
- data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +6 -7
- data/lib/reek/smell_detectors/module_initialize.rb +1 -2
- data/lib/reek/smell_detectors/nested_iterators.rb +6 -6
- data/lib/reek/smell_detectors/nil_check.rb +0 -1
- data/lib/reek/smell_detectors/repeated_conditional.rb +3 -4
- data/lib/reek/smell_detectors/subclassed_from_core_class.rb +0 -1
- data/lib/reek/smell_detectors/too_many_constants.rb +2 -3
- data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -2
- data/lib/reek/smell_detectors/too_many_methods.rb +1 -2
- data/lib/reek/smell_detectors/too_many_statements.rb +1 -2
- data/lib/reek/smell_detectors/uncommunicative_method_name.rb +2 -3
- data/lib/reek/smell_detectors/uncommunicative_module_name.rb +2 -3
- data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +2 -3
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +6 -7
- data/lib/reek/smell_detectors/unused_parameters.rb +0 -1
- data/lib/reek/smell_detectors/unused_private_method.rb +0 -1
- data/lib/reek/smell_detectors/utility_function.rb +2 -3
- data/lib/reek/smell_detectors.rb +1 -2
- data/lib/reek/smell_warning.rb +15 -8
- data/lib/reek/source/source_code.rb +50 -72
- data/lib/reek/source/source_locator.rb +7 -7
- data/lib/reek/spec/should_reek.rb +2 -2
- data/lib/reek/spec/should_reek_of.rb +9 -16
- data/lib/reek/spec/should_reek_only_of.rb +4 -4
- data/lib/reek/spec.rb +6 -6
- data/lib/reek/tree_dresser.rb +5 -5
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +5 -5
- data/samples/checkstyle.xml +1 -1
- data/samples/configuration/accepts_rejects_and_excludes_for_detectors.reek.yml +29 -0
- data/samples/configuration/accepts_rejects_and_excludes_for_directory_directives.reek.yml +30 -0
- data/samples/configuration/full_configuration.reek +8 -4
- data/samples/configuration/full_mask.reek +5 -4
- data/samples/configuration/partial_mask.reek +3 -2
- data/samples/configuration/regular_configuration/.reek.yml +4 -0
- data/samples/paths.rb +5 -4
- data/samples/source_with_hidden_directories/.hidden/hidden.rb +1 -0
- data/samples/source_with_hidden_directories/not_hidden.rb +1 -0
- data/spec/factories/factories.rb +2 -13
- data/spec/reek/ast/node_spec.rb +103 -10
- data/spec/reek/ast/reference_collector_spec.rb +1 -1
- data/spec/reek/ast/sexp_extensions_spec.rb +2 -2
- data/spec/reek/cli/application_spec.rb +50 -38
- data/spec/reek/cli/command/todo_list_command_spec.rb +6 -4
- data/spec/reek/cli/silencer_spec.rb +28 -0
- data/spec/reek/code_comment_spec.rb +31 -38
- data/spec/reek/configuration/app_configuration_spec.rb +46 -33
- data/spec/reek/configuration/configuration_file_finder_spec.rb +133 -49
- data/spec/reek/configuration/default_directive_spec.rb +1 -1
- data/spec/reek/configuration/directory_directives_spec.rb +6 -7
- data/spec/reek/configuration/excluded_paths_spec.rb +6 -6
- data/spec/reek/configuration/rake_task_converter_spec.rb +33 -0
- data/spec/reek/configuration/schema_validator_spec.rb +165 -0
- data/spec/reek/context/code_context_spec.rb +60 -96
- data/spec/reek/context/ghost_context_spec.rb +1 -1
- data/spec/reek/context/root_context_spec.rb +1 -1
- data/spec/reek/documentation_link_spec.rb +20 -0
- data/spec/reek/errors/base_error_spec.rb +13 -0
- data/spec/reek/examiner_spec.rb +100 -30
- data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +82 -80
- data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
- data/spec/reek/report/json_report_spec.rb +13 -46
- data/spec/reek/report/{formatter/location_formatter_spec.rb → location_formatter_spec.rb} +5 -5
- data/spec/reek/report/{formatter/progress_formatter_spec.rb → progress_formatter_spec.rb} +4 -4
- data/spec/reek/report/text_report_spec.rb +4 -4
- data/spec/reek/report/xml_report_spec.rb +3 -3
- data/spec/reek/report/yaml_report_spec.rb +9 -38
- data/spec/reek/report_spec.rb +3 -3
- data/spec/reek/smell_detectors/boolean_parameter_spec.rb +2 -2
- data/spec/reek/smell_detectors/class_variable_spec.rb +26 -32
- data/spec/reek/smell_detectors/control_parameter_spec.rb +34 -4
- data/spec/reek/smell_detectors/duplicate_method_call_spec.rb +3 -3
- data/spec/reek/smell_detectors/feature_envy_spec.rb +47 -2
- data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +9 -9
- data/spec/reek/smell_detectors/module_initialize_spec.rb +14 -0
- data/spec/reek/smell_detectors/nested_iterators_spec.rb +1 -1
- data/spec/reek/smell_detectors/too_many_constants_spec.rb +3 -3
- data/spec/reek/smell_detectors/too_many_instance_variables_spec.rb +1 -1
- data/spec/reek/smell_detectors/uncommunicative_method_name_spec.rb +6 -6
- data/spec/reek/smell_detectors/uncommunicative_module_name_spec.rb +6 -4
- data/spec/reek/smell_detectors/uncommunicative_parameter_name_spec.rb +6 -4
- data/spec/reek/smell_detectors/uncommunicative_variable_name_spec.rb +9 -9
- data/spec/reek/smell_detectors/unused_parameters_spec.rb +3 -3
- data/spec/reek/smell_detectors/unused_private_method_spec.rb +10 -10
- data/spec/reek/smell_detectors/utility_function_spec.rb +5 -5
- data/spec/reek/smell_warning_spec.rb +12 -8
- data/spec/reek/source/source_code_spec.rb +17 -43
- data/spec/reek/source/source_locator_spec.rb +17 -17
- data/spec/reek/spec/should_reek_of_spec.rb +7 -11
- data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
- data/spec/reek/spec/should_reek_spec.rb +3 -3
- data/spec/reek/spec/smell_matcher_spec.rb +3 -3
- data/spec/reek/tree_dresser_spec.rb +12 -17
- data/spec/spec_helper.rb +6 -17
- data/tasks/configuration.rake +8 -5
- metadata +71 -41
- data/defaults.reek +0 -131
- data/features/configuration_files/warn_about_multiple_configuration_files.feature +0 -44
- data/lib/reek/report/formatter/heading_formatter.rb +0 -52
- data/lib/reek/report/formatter/location_formatter.rb +0 -42
- data/lib/reek/report/formatter/progress_formatter.rb +0 -81
- data/lib/reek/report/formatter/simple_warning_formatter.rb +0 -35
- data/lib/reek/report/formatter/wiki_link_warning_formatter.rb +0 -36
- data/lib/reek/report/formatter.rb +0 -33
- data/lib/reek/smell_detectors/syntax.rb +0 -37
- data/samples/configuration/non_public_modifiers_mask.reek +0 -3
- data/samples/smelly_with_inline_mask.rb +0 -8
- data/samples/smelly_with_modifiers.rb +0 -12
- data/samples/source_with_hidden_directories/.hidden/uncommunicative_method_name.rb +0 -5
- data/samples/source_with_non_ruby_files/uncommunicative_parameter_name.rb +0 -6
- data/spec/reek/smell_detectors/syntax_spec.rb +0 -17
- /data/{samples/configuration/more_than_one_configuration_file/regular.reek → .reek.yml} +0 -0
- /data/samples/{clean.rb → clean_source/clean.rb} +0 -0
- /data/samples/{exceptions.reek → configuration/home/home.reek.yml} +0 -0
- /data/samples/configuration/{more_than_one_configuration_file/todo.reek → regular_configuration/empty_sub_directory/.gitignore} +0 -0
- /data/samples/{configuration/single_configuration_file/.reek → no_config_file/.keep} +0 -0
- /data/samples/{inline.rb → smelly_source/inline.rb} +0 -0
- /data/samples/{optparse.rb → smelly_source/optparse.rb} +0 -0
- /data/samples/{redcloth.rb → smelly_source/redcloth.rb} +0 -0
- /data/samples/{smelly.rb → smelly_source/smelly.rb} +0 -0
- /data/samples/{source_with_hidden_directories/uncommunicative_parameter_name.rb → source_with_non_ruby_files/ruby.rb} +0 -0
data/lib/reek/report.rb
CHANGED
|
@@ -6,7 +6,12 @@ require_relative 'report/json_report'
|
|
|
6
6
|
require_relative 'report/text_report'
|
|
7
7
|
require_relative 'report/xml_report'
|
|
8
8
|
require_relative 'report/yaml_report'
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
require_relative 'report/heading_formatter'
|
|
11
|
+
require_relative 'report/location_formatter'
|
|
12
|
+
require_relative 'report/progress_formatter'
|
|
13
|
+
require_relative 'report/simple_warning_formatter'
|
|
14
|
+
require_relative 'report/documentation_link_warning_formatter'
|
|
10
15
|
|
|
11
16
|
module Reek
|
|
12
17
|
# Reek reporting functionality.
|
|
@@ -21,24 +26,24 @@ module Reek
|
|
|
21
26
|
}.freeze
|
|
22
27
|
|
|
23
28
|
LOCATION_FORMATTERS = {
|
|
24
|
-
single_line:
|
|
25
|
-
plain:
|
|
26
|
-
numbers:
|
|
29
|
+
single_line: SingleLineLocationFormatter,
|
|
30
|
+
plain: BlankLocationFormatter,
|
|
31
|
+
numbers: DefaultLocationFormatter
|
|
27
32
|
}.freeze
|
|
28
33
|
|
|
29
34
|
HEADING_FORMATTERS = {
|
|
30
|
-
verbose:
|
|
31
|
-
quiet:
|
|
35
|
+
verbose: VerboseHeadingFormatter,
|
|
36
|
+
quiet: QuietHeadingFormatter
|
|
32
37
|
}.freeze
|
|
33
38
|
|
|
34
39
|
PROGRESS_FORMATTERS = {
|
|
35
|
-
dots:
|
|
36
|
-
quiet:
|
|
40
|
+
dots: ProgressFormatter::Dots,
|
|
41
|
+
quiet: ProgressFormatter::Quiet
|
|
37
42
|
}.freeze
|
|
38
43
|
|
|
39
44
|
WARNING_FORMATTER_CLASSES = {
|
|
40
|
-
|
|
41
|
-
simple:
|
|
45
|
+
documentation_links: DocumentationLinkWarningFormatter,
|
|
46
|
+
simple: SimpleWarningFormatter
|
|
42
47
|
}.freeze
|
|
43
48
|
|
|
44
49
|
# Map report format symbol to a report class.
|
|
@@ -7,11 +7,11 @@ module Reek
|
|
|
7
7
|
class SmellConfiguration
|
|
8
8
|
# The name of the config field that specifies whether a smell is
|
|
9
9
|
# enabled. Set to +true+ or +false+.
|
|
10
|
-
ENABLED_KEY = 'enabled'
|
|
10
|
+
ENABLED_KEY = 'enabled'
|
|
11
11
|
|
|
12
12
|
# The name of the config field that sets scope-specific overrides
|
|
13
13
|
# for other values in the current smell detector's configuration.
|
|
14
|
-
OVERRIDES_KEY = 'overrides'
|
|
14
|
+
OVERRIDES_KEY = 'overrides'
|
|
15
15
|
|
|
16
16
|
def initialize(hash)
|
|
17
17
|
@options = hash
|
|
@@ -15,14 +15,14 @@ module Reek
|
|
|
15
15
|
# - {file:README.md}
|
|
16
16
|
# for details.
|
|
17
17
|
#
|
|
18
|
-
# :reek:UnusedPrivateMethod
|
|
19
|
-
# :reek:TooManyMethods
|
|
18
|
+
# @quality :reek:UnusedPrivateMethod { exclude: [ smell_warning ] }
|
|
19
|
+
# @quality :reek:TooManyMethods { max_methods: 18 }
|
|
20
20
|
class BaseDetector
|
|
21
21
|
attr_reader :config
|
|
22
22
|
# The name of the config field that lists the names of code contexts
|
|
23
23
|
# that should not be checked. Add this field to the config for each
|
|
24
24
|
# smell that should ignore this code element.
|
|
25
|
-
EXCLUDE_KEY = 'exclude'
|
|
25
|
+
EXCLUDE_KEY = 'exclude'
|
|
26
26
|
|
|
27
27
|
# The default value for the +EXCLUDE_KEY+ if it isn't specified
|
|
28
28
|
# in any configuration file.
|
|
@@ -59,7 +59,7 @@ module Reek
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def source_line
|
|
62
|
-
@
|
|
62
|
+
@source_line ||= expression.line
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
def exception?
|
|
@@ -78,12 +78,9 @@ module Reek
|
|
|
78
78
|
ctx.config_for(self.class)
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
def smell_warning(options = {})
|
|
83
|
-
context = options.fetch(:context)
|
|
84
|
-
exp = context.exp
|
|
81
|
+
def smell_warning(**options)
|
|
85
82
|
SmellWarning.new(self,
|
|
86
|
-
source:
|
|
83
|
+
source: expression.source,
|
|
87
84
|
context: context.full_name,
|
|
88
85
|
lines: options.fetch(:lines),
|
|
89
86
|
message: options.fetch(:message),
|
|
@@ -99,7 +96,7 @@ module Reek
|
|
|
99
96
|
[:def, :defs]
|
|
100
97
|
end
|
|
101
98
|
|
|
102
|
-
# :reek:UtilityFunction
|
|
99
|
+
# @quality :reek:UtilityFunction
|
|
103
100
|
def default_config
|
|
104
101
|
{
|
|
105
102
|
SmellConfiguration::ENABLED_KEY => true,
|
|
@@ -138,14 +135,14 @@ module Reek
|
|
|
138
135
|
# Note that we assume a valid name - exceptions are not handled here.
|
|
139
136
|
#
|
|
140
137
|
# @param detector_name [String] the detector in question, e.g. 'DuplicateMethodCall'
|
|
141
|
-
# @return [SmellDetector]
|
|
138
|
+
# @return [SmellDetector] this will return the class, not an instance
|
|
142
139
|
#
|
|
143
140
|
def to_detector(detector_name)
|
|
144
141
|
SmellDetectors.const_get detector_name
|
|
145
142
|
end
|
|
146
143
|
|
|
147
144
|
#
|
|
148
|
-
# @return [Set<Symbol>]
|
|
145
|
+
# @return [Set<Symbol>] all configuration keys that are available for this detector
|
|
149
146
|
#
|
|
150
147
|
def configuration_keys
|
|
151
148
|
Set.new(default_config.keys.map(&:to_sym))
|
|
@@ -25,9 +25,9 @@ module Reek
|
|
|
25
25
|
# @return [Array<SmellWarning>]
|
|
26
26
|
#
|
|
27
27
|
def sniff
|
|
28
|
-
class_variables_in_context.map do |variable,
|
|
28
|
+
class_variables_in_context.map do |variable, occurences|
|
|
29
|
+
lines = occurences.map(&:line)
|
|
29
30
|
smell_warning(
|
|
30
|
-
context: context,
|
|
31
31
|
lines: lines,
|
|
32
32
|
message: "declares the class variable '#{variable}'",
|
|
33
33
|
parameters: { name: variable.to_s })
|
|
@@ -38,16 +38,8 @@ module Reek
|
|
|
38
38
|
# Collects the names of the class variables declared and/or used
|
|
39
39
|
# in the given module.
|
|
40
40
|
#
|
|
41
|
-
# :reek:TooManyStatements: { max_statements: 7 }
|
|
42
41
|
def class_variables_in_context
|
|
43
|
-
|
|
44
|
-
collector = proc do |cvar_node|
|
|
45
|
-
result[cvar_node.name].push(cvar_node.line)
|
|
46
|
-
end
|
|
47
|
-
[:cvar, :cvasgn, :cvdecl].each do |stmt_type|
|
|
48
|
-
expression.each_node(stmt_type, [:class, :module], &collector)
|
|
49
|
-
end
|
|
50
|
-
result
|
|
42
|
+
context.local_nodes([:cvar, :cvasgn, :cvdecl]).group_by(&:name)
|
|
51
43
|
end
|
|
52
44
|
end
|
|
53
45
|
end
|
|
@@ -49,18 +49,32 @@ module Reek
|
|
|
49
49
|
#
|
|
50
50
|
# @return [Array<SmellWarning>]
|
|
51
51
|
#
|
|
52
|
-
# :reek:FeatureEnvy
|
|
53
52
|
def sniff
|
|
54
|
-
|
|
53
|
+
control_parameters.map do |control_parameter|
|
|
55
54
|
argument = control_parameter.name.to_s
|
|
56
55
|
smell_warning(
|
|
57
|
-
context: context,
|
|
58
56
|
lines: control_parameter.lines,
|
|
59
57
|
message: "is controlled by argument '#{argument}'",
|
|
60
58
|
parameters: { argument: argument })
|
|
61
59
|
end
|
|
62
60
|
end
|
|
63
61
|
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def control_parameters
|
|
65
|
+
potential_parameters.
|
|
66
|
+
map { |param| FoundControlParameter.new(param, find_matches(param)) }.
|
|
67
|
+
select(&:smells?)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def potential_parameters
|
|
71
|
+
expression.parameter_names
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def find_matches(param)
|
|
75
|
+
ControlParameterFinder.new(expression, param).find_matches
|
|
76
|
+
end
|
|
77
|
+
|
|
64
78
|
#
|
|
65
79
|
# Collects information about a single control parameter.
|
|
66
80
|
#
|
|
@@ -166,35 +180,6 @@ module Reek
|
|
|
166
180
|
end
|
|
167
181
|
|
|
168
182
|
private_constant :ControlParameterFinder
|
|
169
|
-
|
|
170
|
-
#
|
|
171
|
-
# Collects all control parameters in a given context.
|
|
172
|
-
#
|
|
173
|
-
class ControlParameterCollector
|
|
174
|
-
def initialize(context)
|
|
175
|
-
@context = context
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def control_parameters
|
|
179
|
-
potential_parameters.
|
|
180
|
-
map { |param| FoundControlParameter.new(param, find_matches(param)) }.
|
|
181
|
-
select(&:smells?)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
private
|
|
185
|
-
|
|
186
|
-
attr_reader :context
|
|
187
|
-
|
|
188
|
-
def potential_parameters
|
|
189
|
-
context.exp.parameter_names
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def find_matches(param)
|
|
193
|
-
ControlParameterFinder.new(context.exp, param).find_matches
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
private_constant :ControlParameterCollector
|
|
198
183
|
end
|
|
199
184
|
end
|
|
200
185
|
end
|
|
@@ -24,7 +24,7 @@ module Reek
|
|
|
24
24
|
# reported as a DataClump unless there are more than this many
|
|
25
25
|
# methods containing those parameters.
|
|
26
26
|
#
|
|
27
|
-
MAX_COPIES_KEY = 'max_copies'
|
|
27
|
+
MAX_COPIES_KEY = 'max_copies'
|
|
28
28
|
DEFAULT_MAX_COPIES = 2
|
|
29
29
|
|
|
30
30
|
#
|
|
@@ -32,7 +32,7 @@ module Reek
|
|
|
32
32
|
# size. No group of common parameters will be reported as
|
|
33
33
|
# a DataClump unless it contains at least this many parameters.
|
|
34
34
|
#
|
|
35
|
-
MIN_CLUMP_SIZE_KEY = 'min_clump_size'
|
|
35
|
+
MIN_CLUMP_SIZE_KEY = 'min_clump_size'
|
|
36
36
|
DEFAULT_MIN_CLUMP_SIZE = 2
|
|
37
37
|
|
|
38
38
|
def self.contexts # :nodoc:
|
|
@@ -54,7 +54,6 @@ module Reek
|
|
|
54
54
|
MethodGroup.new(context, min_clump_size, max_copies).clumps.map do |clump, methods|
|
|
55
55
|
methods_length = methods.length
|
|
56
56
|
smell_warning(
|
|
57
|
-
context: context,
|
|
58
57
|
lines: methods.map(&:line),
|
|
59
58
|
message: "takes parameters #{DataClump.print_clump(clump)} " \
|
|
60
59
|
"to #{methods_length} methods",
|
|
@@ -101,7 +100,7 @@ module Reek
|
|
|
101
100
|
end.uniq
|
|
102
101
|
end
|
|
103
102
|
|
|
104
|
-
# :reek:UtilityFunction
|
|
103
|
+
# @quality :reek:UtilityFunction
|
|
105
104
|
def common_argument_names_for(methods)
|
|
106
105
|
methods.map(&:arg_names).inject(:&)
|
|
107
106
|
end
|
|
@@ -21,13 +21,13 @@ module Reek
|
|
|
21
21
|
class DuplicateMethodCall < BaseDetector
|
|
22
22
|
# The name of the config field that sets the maximum number of
|
|
23
23
|
# identical calls to be permitted within any single method.
|
|
24
|
-
MAX_ALLOWED_CALLS_KEY = 'max_calls'
|
|
24
|
+
MAX_ALLOWED_CALLS_KEY = 'max_calls'
|
|
25
25
|
DEFAULT_MAX_CALLS = 1
|
|
26
26
|
|
|
27
27
|
# The name of the config field that sets the names of any
|
|
28
28
|
# methods for which identical calls should be to be permitted
|
|
29
29
|
# within any single method.
|
|
30
|
-
ALLOW_CALLS_KEY = 'allow_calls'
|
|
30
|
+
ALLOW_CALLS_KEY = 'allow_calls'
|
|
31
31
|
DEFAULT_ALLOW_CALLS = [].freeze
|
|
32
32
|
|
|
33
33
|
def self.default_config
|
|
@@ -47,7 +47,6 @@ module Reek
|
|
|
47
47
|
call = found_call.call
|
|
48
48
|
occurs = found_call.occurs
|
|
49
49
|
smell_warning(
|
|
50
|
-
context: context,
|
|
51
50
|
lines: found_call.lines,
|
|
52
51
|
message: "calls '#{call}' #{occurs} times",
|
|
53
52
|
parameters: { name: call, count: occurs })
|
|
@@ -118,10 +117,10 @@ module Reek
|
|
|
118
117
|
|
|
119
118
|
attr_reader :allow_calls, :max_allowed_calls
|
|
120
119
|
|
|
121
|
-
# :reek:TooManyStatements
|
|
122
|
-
# :reek:DuplicateMethodCall
|
|
120
|
+
# @quality :reek:TooManyStatements { max_statements: 6 }
|
|
121
|
+
# @quality :reek:DuplicateMethodCall { max_calls: 2 }
|
|
123
122
|
def collect_calls(result)
|
|
124
|
-
context.
|
|
123
|
+
context.local_nodes(:send, [:mlhs]) do |call_node|
|
|
125
124
|
next if call_node.object_creation_call?
|
|
126
125
|
next if simple_method_call? call_node
|
|
127
126
|
result[call_node].record(call_node)
|
|
@@ -135,7 +134,7 @@ module Reek
|
|
|
135
134
|
found_call.occurs > max_allowed_calls && !allow_calls?(found_call.call)
|
|
136
135
|
end
|
|
137
136
|
|
|
138
|
-
# :reek:UtilityFunction
|
|
137
|
+
# @quality :reek:UtilityFunction
|
|
139
138
|
def simple_method_call?(call_node)
|
|
140
139
|
!call_node.receiver && call_node.args.empty?
|
|
141
140
|
end
|
|
@@ -43,10 +43,10 @@ module Reek
|
|
|
43
43
|
# @return [Array<SmellWarning>]
|
|
44
44
|
#
|
|
45
45
|
def sniff
|
|
46
|
+
return [] if context.singleton_method? || context.module_function?
|
|
46
47
|
return [] unless context.references_self?
|
|
47
48
|
envious_receivers.map do |name, lines|
|
|
48
49
|
smell_warning(
|
|
49
|
-
context: context,
|
|
50
50
|
lines: lines,
|
|
51
51
|
message: "refers to '#{name}' more than self (maybe move it to another class?)",
|
|
52
52
|
parameters: { name: name.to_s })
|
|
@@ -37,7 +37,6 @@ module Reek
|
|
|
37
37
|
message = "assumes too much for instance variable '#{assumption}'"
|
|
38
38
|
|
|
39
39
|
smell_warning(
|
|
40
|
-
context: context,
|
|
41
40
|
lines: [source_line],
|
|
42
41
|
message: message,
|
|
43
42
|
parameters: { assumption: assumption.to_s })
|
|
@@ -55,17 +54,9 @@ module Reek
|
|
|
55
54
|
|
|
56
55
|
def variables_from_context
|
|
57
56
|
method_expressions.map do |method|
|
|
58
|
-
method.
|
|
57
|
+
method.each_node(:ivar, [:or_asgn]).map(&:name)
|
|
59
58
|
end.flatten
|
|
60
59
|
end
|
|
61
|
-
|
|
62
|
-
def assumption_nodes
|
|
63
|
-
[:ivar]
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def ignored_nodes
|
|
67
|
-
[:or_asgn]
|
|
68
|
-
end
|
|
69
60
|
end
|
|
70
61
|
end
|
|
71
62
|
end
|
|
@@ -16,7 +16,7 @@ module Reek
|
|
|
16
16
|
class LongParameterList < BaseDetector
|
|
17
17
|
# The name of the config field that sets the maximum number of
|
|
18
18
|
# parameters permitted in any method or block.
|
|
19
|
-
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
|
19
|
+
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
|
20
20
|
DEFAULT_MAX_ALLOWED_PARAMS = 3
|
|
21
21
|
|
|
22
22
|
def self.default_config
|
|
@@ -36,7 +36,6 @@ module Reek
|
|
|
36
36
|
count = expression.arg_names.length
|
|
37
37
|
return [] if count <= max_allowed_params
|
|
38
38
|
[smell_warning(
|
|
39
|
-
context: context,
|
|
40
39
|
lines: [source_line],
|
|
41
40
|
message: "has #{count} parameters",
|
|
42
41
|
parameters: { count: count })]
|
|
@@ -12,7 +12,7 @@ module Reek
|
|
|
12
12
|
class LongYieldList < BaseDetector
|
|
13
13
|
# The name of the config field that sets the maximum number of
|
|
14
14
|
# parameters permitted in any method or block.
|
|
15
|
-
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
|
15
|
+
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
|
16
16
|
DEFAULT_MAX_ALLOWED_PARAMS = 3
|
|
17
17
|
|
|
18
18
|
def self.default_config
|
|
@@ -24,14 +24,13 @@ module Reek
|
|
|
24
24
|
#
|
|
25
25
|
# @return [Array<SmellWarning>]
|
|
26
26
|
#
|
|
27
|
-
# :reek:DuplicateMethodCall
|
|
27
|
+
# @quality :reek:DuplicateMethodCall { max_calls: 2 }
|
|
28
28
|
def sniff
|
|
29
29
|
context.local_nodes(:yield).select do |yield_node|
|
|
30
30
|
yield_node.args.length > max_allowed_params
|
|
31
31
|
end.map do |yield_node|
|
|
32
32
|
count = yield_node.args.length
|
|
33
33
|
smell_warning(
|
|
34
|
-
context: context,
|
|
35
34
|
lines: [yield_node.line],
|
|
36
35
|
message: "yields #{count} parameters",
|
|
37
36
|
parameters: { count: count })
|
|
@@ -12,7 +12,7 @@ module Reek
|
|
|
12
12
|
#
|
|
13
13
|
# See {file:docs/Manual-Dispatch.md} for details.
|
|
14
14
|
class ManualDispatch < BaseDetector
|
|
15
|
-
MESSAGE = 'manually dispatches method call'
|
|
15
|
+
MESSAGE = 'manually dispatches method call'
|
|
16
16
|
|
|
17
17
|
#
|
|
18
18
|
# Checks for +respond_to?+ usage within the given method
|
|
@@ -20,10 +20,10 @@ module Reek
|
|
|
20
20
|
# @return [Array<SmellWarning>]
|
|
21
21
|
#
|
|
22
22
|
def sniff
|
|
23
|
-
smelly_nodes = context.
|
|
23
|
+
smelly_nodes = context.local_nodes(:send).select { |node| node.name == :respond_to? }
|
|
24
24
|
return [] if smelly_nodes.empty?
|
|
25
25
|
lines = smelly_nodes.map(&:line)
|
|
26
|
-
[smell_warning(
|
|
26
|
+
[smell_warning(lines: lines, message: MESSAGE)]
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
@@ -21,10 +21,10 @@ module Reek
|
|
|
21
21
|
# gets distorted and diluted, and ! ceases to convey any information
|
|
22
22
|
# whatsoever.
|
|
23
23
|
#
|
|
24
|
-
# Such a method is called
|
|
24
|
+
# Such a method is called MissingSafeMethod and is reported as a smell.
|
|
25
25
|
#
|
|
26
|
-
# See {file:docs/
|
|
27
|
-
class
|
|
26
|
+
# See {file:docs/Missing-Safe-Method.md} for details.
|
|
27
|
+
class MissingSafeMethod < BaseDetector
|
|
28
28
|
def self.contexts # :nodoc:
|
|
29
29
|
[:class]
|
|
30
30
|
end
|
|
@@ -48,20 +48,19 @@ module Reek
|
|
|
48
48
|
#
|
|
49
49
|
def sniff
|
|
50
50
|
context.node_instance_methods.select do |method_sexp|
|
|
51
|
-
|
|
51
|
+
missing_safe_method?(method_sexp)
|
|
52
52
|
end.map do |method_sexp|
|
|
53
53
|
name = method_sexp.name
|
|
54
54
|
smell_warning(
|
|
55
|
-
context: context,
|
|
56
55
|
lines: [method_sexp.line],
|
|
57
|
-
message: "has
|
|
56
|
+
message: "has missing safe method '#{name}'",
|
|
58
57
|
parameters: { name: name.to_s })
|
|
59
58
|
end
|
|
60
59
|
end
|
|
61
60
|
|
|
62
61
|
private
|
|
63
62
|
|
|
64
|
-
def
|
|
63
|
+
def missing_safe_method?(method_sexp)
|
|
65
64
|
return false unless method_sexp.ends_with_bang?
|
|
66
65
|
return false if ignore_method? method_sexp
|
|
67
66
|
return false if version_without_bang_exists? method_sexp
|
|
@@ -21,10 +21,9 @@ module Reek
|
|
|
21
21
|
# @return [Array<SmellWarning>]
|
|
22
22
|
#
|
|
23
23
|
def sniff
|
|
24
|
-
context.
|
|
24
|
+
context.defined_instance_methods.each do |node|
|
|
25
25
|
if node.name == :initialize
|
|
26
26
|
return smell_warning(
|
|
27
|
-
context: context,
|
|
28
27
|
lines: [source_line],
|
|
29
28
|
message: 'has initialize method')
|
|
30
29
|
end
|
|
@@ -20,12 +20,12 @@ module Reek
|
|
|
20
20
|
|
|
21
21
|
# The name of the config field that sets the maximum depth
|
|
22
22
|
# of nested iterators to be permitted within any single method.
|
|
23
|
-
MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'
|
|
23
|
+
MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'
|
|
24
24
|
DEFAULT_MAX_ALLOWED_NESTING = 1
|
|
25
25
|
|
|
26
26
|
# The name of the config field that sets the names of any
|
|
27
27
|
# methods for which nesting should not be considered
|
|
28
|
-
IGNORE_ITERATORS_KEY = 'ignore_iterators'
|
|
28
|
+
IGNORE_ITERATORS_KEY = 'ignore_iterators'
|
|
29
29
|
DEFAULT_IGNORE_ITERATORS = ['tap'].freeze
|
|
30
30
|
|
|
31
31
|
def self.default_config
|
|
@@ -45,7 +45,6 @@ module Reek
|
|
|
45
45
|
find_violations.group_by(&:depth).map do |depth, group|
|
|
46
46
|
lines = group.map(&:line)
|
|
47
47
|
smell_warning(
|
|
48
|
-
context: context,
|
|
49
48
|
lines: lines,
|
|
50
49
|
message: "contains iterators nested #{depth} deep",
|
|
51
50
|
parameters: { depth: depth })
|
|
@@ -94,10 +93,11 @@ module Reek
|
|
|
94
93
|
#
|
|
95
94
|
# @return [Array<Iterator>]
|
|
96
95
|
#
|
|
97
|
-
# :reek:TooManyStatements
|
|
96
|
+
# @quality :reek:TooManyStatements { max_statements: 6 }
|
|
98
97
|
def scout(exp:, depth:)
|
|
99
98
|
return [] unless exp
|
|
100
|
-
|
|
99
|
+
# Find all non-nested blocks in this expression
|
|
100
|
+
exp.each_node([:block], [:block]).flat_map do |iterator|
|
|
101
101
|
new_depth = increment_depth(iterator, depth)
|
|
102
102
|
# 1st case: we recurse down the given block of the iterator. In this case
|
|
103
103
|
# we need to check if we should increment the depth.
|
|
@@ -125,7 +125,7 @@ module Reek
|
|
|
125
125
|
@max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context)
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
# :reek:FeatureEnvy
|
|
128
|
+
# @quality :reek:FeatureEnvy
|
|
129
129
|
def ignored_iterator?(exp)
|
|
130
130
|
ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.name } ||
|
|
131
131
|
exp.without_block_arguments?
|
|
@@ -28,7 +28,7 @@ module Reek
|
|
|
28
28
|
class RepeatedConditional < BaseDetector
|
|
29
29
|
# The name of the config field that sets the maximum number of
|
|
30
30
|
# identical conditionals permitted within any single class.
|
|
31
|
-
MAX_IDENTICAL_IFS_KEY = 'max_ifs'
|
|
31
|
+
MAX_IDENTICAL_IFS_KEY = 'max_ifs'
|
|
32
32
|
DEFAULT_MAX_IFS = 2
|
|
33
33
|
|
|
34
34
|
BLOCK_GIVEN_CONDITION = ::Parser::AST::Node.new(:send, [nil, :block_given?])
|
|
@@ -46,7 +46,7 @@ module Reek
|
|
|
46
46
|
#
|
|
47
47
|
# @return [Array<SmellWarning>]
|
|
48
48
|
#
|
|
49
|
-
# :reek:DuplicateMethodCall
|
|
49
|
+
# @quality :reek:DuplicateMethodCall { max_calls: 2 }
|
|
50
50
|
def sniff
|
|
51
51
|
conditional_counts.select do |_key, lines|
|
|
52
52
|
lines.length > max_identical_ifs
|
|
@@ -54,7 +54,6 @@ module Reek
|
|
|
54
54
|
occurs = lines.length
|
|
55
55
|
expression = key.format_to_ruby
|
|
56
56
|
smell_warning(
|
|
57
|
-
context: context,
|
|
58
57
|
lines: lines,
|
|
59
58
|
message: "tests '#{expression}' at least #{occurs} times",
|
|
60
59
|
parameters: { name: expression, count: occurs })
|
|
@@ -72,7 +71,7 @@ module Reek
|
|
|
72
71
|
# the given syntax tree together with the number of times each
|
|
73
72
|
# occurs. Ignores nested classes and modules.
|
|
74
73
|
#
|
|
75
|
-
# :reek:TooManyStatements
|
|
74
|
+
# @quality :reek:TooManyStatements { max_statements: 9 }
|
|
76
75
|
def conditional_counts
|
|
77
76
|
result = Hash.new { |hash, key| hash[key] = [] }
|
|
78
77
|
collector = proc do |node|
|
|
@@ -15,7 +15,7 @@ module Reek
|
|
|
15
15
|
class TooManyConstants < BaseDetector
|
|
16
16
|
# The name of the config field that sets the maximum number
|
|
17
17
|
# of constants permitted in a class.
|
|
18
|
-
MAX_ALLOWED_CONSTANTS_KEY = 'max_constants'
|
|
18
|
+
MAX_ALLOWED_CONSTANTS_KEY = 'max_constants'
|
|
19
19
|
DEFAULT_MAX_CONSTANTS = 5
|
|
20
20
|
IGNORED_NODES = [:module, :class].freeze
|
|
21
21
|
|
|
@@ -35,7 +35,7 @@ module Reek
|
|
|
35
35
|
# @return [Array<SmellWarning>]
|
|
36
36
|
#
|
|
37
37
|
def sniff
|
|
38
|
-
count = context.
|
|
38
|
+
count = context.local_nodes(:casgn).reject(&:defines_module?).length
|
|
39
39
|
|
|
40
40
|
return [] if count <= max_allowed_constants
|
|
41
41
|
|
|
@@ -50,7 +50,6 @@ module Reek
|
|
|
50
50
|
|
|
51
51
|
def build_smell_warning(count)
|
|
52
52
|
[smell_warning(
|
|
53
|
-
context: context,
|
|
54
53
|
lines: [source_line],
|
|
55
54
|
message: "has #{count} constants",
|
|
56
55
|
parameters: { count: count })]
|
|
@@ -15,7 +15,7 @@ module Reek
|
|
|
15
15
|
class TooManyInstanceVariables < BaseDetector
|
|
16
16
|
# The name of the config field that sets the maximum number of instance
|
|
17
17
|
# variables permitted in a class.
|
|
18
|
-
MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
|
|
18
|
+
MAX_ALLOWED_IVARS_KEY = 'max_instance_variables'
|
|
19
19
|
DEFAULT_MAX_IVARS = 4
|
|
20
20
|
|
|
21
21
|
def self.contexts
|
|
@@ -38,7 +38,6 @@ module Reek
|
|
|
38
38
|
count = variables.uniq.size
|
|
39
39
|
return [] if count <= max_allowed_ivars
|
|
40
40
|
[smell_warning(
|
|
41
|
-
context: context,
|
|
42
41
|
lines: [source_line],
|
|
43
42
|
message: "has at least #{count} instance variables",
|
|
44
43
|
parameters: { count: count })]
|
|
@@ -17,7 +17,7 @@ module Reek
|
|
|
17
17
|
class TooManyMethods < BaseDetector
|
|
18
18
|
# The name of the config field that sets the maximum number of methods
|
|
19
19
|
# permitted in a class.
|
|
20
|
-
MAX_ALLOWED_METHODS_KEY = 'max_methods'
|
|
20
|
+
MAX_ALLOWED_METHODS_KEY = 'max_methods'
|
|
21
21
|
DEFAULT_MAX_METHODS = 15
|
|
22
22
|
|
|
23
23
|
def self.contexts
|
|
@@ -40,7 +40,6 @@ module Reek
|
|
|
40
40
|
actual = context.node_instance_methods.length
|
|
41
41
|
return [] if actual <= max_allowed_methods
|
|
42
42
|
[smell_warning(
|
|
43
|
-
context: context,
|
|
44
43
|
lines: [source_line],
|
|
45
44
|
message: "has at least #{actual} methods",
|
|
46
45
|
parameters: { count: actual })]
|