reek 4.7.1 → 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 +36 -7
- data/.simplecov +1 -0
- data/.travis.yml +3 -9
- data/.yardopts +1 -1
- data/CHANGELOG.md +59 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +5 -5
- 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 +7 -7
- 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 +3 -3
- 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 +80 -75
- 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 +3 -3
- data/features/configuration_via_source_comments/well_formed_source_comments.feature +2 -2
- data/features/locales.feature +32 -0
- data/features/rake_task/rake_task.feature +58 -18
- 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 +40 -0
- data/features/samples.feature +287 -287
- data/features/step_definitions/reek_steps.rb +2 -2
- data/features/step_definitions/sample_file_steps.rb +9 -4
- data/features/support/env.rb +2 -11
- data/features/todo_list.feature +26 -23
- data/lib/reek/ast/builder.rb +1 -1
- data/lib/reek/ast/node.rb +40 -58
- 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/ast/sexp_extensions/send.rb +0 -4
- 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 +31 -16
- data/lib/reek/cli/silencer.rb +14 -3
- data/lib/reek/code_comment.rb +14 -16
- data/lib/reek/configuration/app_configuration.rb +32 -27
- data/lib/reek/configuration/configuration_converter.rb +110 -0
- data/lib/reek/configuration/configuration_file_finder.rb +16 -41
- 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 +3 -3
- data/lib/reek/context/code_context.rb +47 -43
- data/lib/reek/context/ghost_context.rb +0 -2
- data/lib/reek/context/method_context.rb +22 -15
- data/lib/reek/context/module_context.rb +5 -9
- data/lib/reek/context/root_context.rb +0 -4
- data/lib/reek/context/send_context.rb +2 -2
- data/lib/reek/context_builder.rb +43 -44
- data/lib/reek/detector_repository.rb +11 -11
- data/lib/reek/documentation_link.rb +28 -0
- data/lib/reek/errors/bad_detector_configuration_key_in_comment_error.rb +14 -13
- data/lib/reek/errors/bad_detector_in_comment_error.rb +12 -11
- 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 +43 -0
- data/lib/reek/errors/garbage_detector_configuration_in_comment_error.rb +13 -12
- data/lib/reek/errors/incomprehensible_source_error.rb +22 -24
- data/lib/reek/errors/syntax_error.rb +41 -0
- data/lib/reek/examiner.rb +24 -26
- data/lib/reek/logging_error_handler.rb +7 -5
- data/lib/reek/rake/task.rb +8 -4
- 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 +5 -11
- data/lib/reek/smell_detectors/base_detector.rb +30 -26
- data/lib/reek/smell_detectors/boolean_parameter.rb +3 -5
- data/lib/reek/smell_detectors/class_variable.rb +5 -14
- data/lib/reek/smell_detectors/control_parameter.rb +18 -33
- data/lib/reek/smell_detectors/data_clump.rb +15 -9
- data/lib/reek/smell_detectors/duplicate_method_call.rb +23 -17
- data/lib/reek/smell_detectors/feature_envy.rb +9 -7
- data/lib/reek/smell_detectors/instance_variable_assumption.rb +14 -23
- data/lib/reek/smell_detectors/irresponsible_module.rb +5 -12
- data/lib/reek/smell_detectors/long_parameter_list.rb +10 -7
- data/lib/reek/smell_detectors/long_yield_list.rb +10 -7
- data/lib/reek/smell_detectors/manual_dispatch.rb +4 -5
- data/lib/reek/smell_detectors/{prima_donna_method.rb → missing_safe_method.rb} +20 -20
- data/lib/reek/smell_detectors/module_initialize.rb +3 -5
- data/lib/reek/smell_detectors/nested_iterators.rb +16 -24
- data/lib/reek/smell_detectors/nil_check.rb +8 -15
- data/lib/reek/smell_detectors/repeated_conditional.rb +13 -11
- data/lib/reek/smell_detectors/subclassed_from_core_class.rb +7 -8
- data/lib/reek/smell_detectors/too_many_constants.rb +10 -9
- data/lib/reek/smell_detectors/too_many_instance_variables.rb +10 -6
- data/lib/reek/smell_detectors/too_many_methods.rb +11 -7
- data/lib/reek/smell_detectors/too_many_statements.rb +10 -6
- data/lib/reek/smell_detectors/uncommunicative_method_name.rb +10 -11
- data/lib/reek/smell_detectors/uncommunicative_module_name.rb +14 -18
- data/lib/reek/smell_detectors/uncommunicative_parameter_name.rb +17 -22
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +26 -27
- data/lib/reek/smell_detectors/unused_parameters.rb +4 -6
- data/lib/reek/smell_detectors/unused_private_method.rb +11 -18
- data/lib/reek/smell_detectors/utility_function.rb +12 -16
- data/lib/reek/smell_detectors.rb +1 -2
- data/lib/reek/smell_warning.rb +15 -8
- data/lib/reek/source/source_code.rb +57 -58
- data/lib/reek/source/source_locator.rb +8 -8
- 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 +8 -8
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +5 -7
- 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 +14 -34
- data/spec/reek/cli/application_spec.rb +52 -42
- 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 -51
- 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 +70 -106
- data/spec/reek/context/ghost_context_spec.rb +9 -9
- data/spec/reek/context/method_context_spec.rb +2 -2
- data/spec/reek/context/module_context_spec.rb +3 -3
- data/spec/reek/context/root_context_spec.rb +1 -1
- data/spec/reek/context_builder_spec.rb +20 -0
- 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 +136 -29
- data/spec/reek/rake/task_spec.rb +25 -2
- 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/base_detector_spec.rb +4 -5
- 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/irresponsible_module_spec.rb +0 -11
- data/spec/reek/smell_detectors/{prima_donna_method_spec.rb → missing_safe_method_spec.rb} +10 -10
- 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 +36 -15
- 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 +21 -10
- data/spec/reek/smell_detectors/utility_function_spec.rb +57 -5
- data/spec/reek/smell_warning_spec.rb +12 -8
- data/spec/reek/source/source_code_spec.rb +22 -21
- data/spec/reek/source/source_locator_spec.rb +17 -17
- data/spec/reek/spec/should_reek_of_spec.rb +25 -29
- data/spec/reek/spec/should_reek_only_of_spec.rb +2 -2
- data/spec/reek/spec/should_reek_spec.rb +8 -8
- data/spec/reek/spec/smell_matcher_spec.rb +23 -23
- data/spec/reek/tree_dresser_spec.rb +12 -17
- data/spec/spec_helper.rb +6 -17
- data/tasks/configuration.rake +8 -5
- metadata +74 -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
|
@@ -15,40 +15,33 @@ 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.
|
|
29
29
|
DEFAULT_EXCLUDE_SET = [].freeze
|
|
30
30
|
|
|
31
|
-
def initialize(
|
|
32
|
-
@config = SmellConfiguration.new self.class.default_config.merge(
|
|
31
|
+
def initialize(configuration: {}, context: nil)
|
|
32
|
+
@config = SmellConfiguration.new self.class.default_config.merge(configuration)
|
|
33
|
+
@context = context
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def smell_type
|
|
36
37
|
self.class.smell_type
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def run_for(context)
|
|
44
|
-
return [] unless enabled_for?(context)
|
|
45
|
-
return [] if exception?(context)
|
|
40
|
+
def run
|
|
41
|
+
return [] unless enabled?
|
|
42
|
+
return [] if exception?
|
|
46
43
|
|
|
47
|
-
sniff
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def exception?(context)
|
|
51
|
-
context.matches?(value(EXCLUDE_KEY, context))
|
|
44
|
+
sniff
|
|
52
45
|
end
|
|
53
46
|
|
|
54
47
|
def self.todo_configuration_for(smells)
|
|
@@ -59,7 +52,21 @@ module Reek
|
|
|
59
52
|
|
|
60
53
|
private
|
|
61
54
|
|
|
62
|
-
|
|
55
|
+
attr_reader :context
|
|
56
|
+
|
|
57
|
+
def expression
|
|
58
|
+
@expression ||= context.exp
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def source_line
|
|
62
|
+
@source_line ||= expression.line
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def exception?
|
|
66
|
+
context.matches?(value(EXCLUDE_KEY, context))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def enabled?
|
|
63
70
|
config.enabled? && config_for(context)[SmellConfiguration::ENABLED_KEY] != false
|
|
64
71
|
end
|
|
65
72
|
|
|
@@ -71,12 +78,9 @@ module Reek
|
|
|
71
78
|
ctx.config_for(self.class)
|
|
72
79
|
end
|
|
73
80
|
|
|
74
|
-
|
|
75
|
-
def smell_warning(options = {})
|
|
76
|
-
context = options.fetch(:context)
|
|
77
|
-
exp = context.exp
|
|
81
|
+
def smell_warning(**options)
|
|
78
82
|
SmellWarning.new(self,
|
|
79
|
-
source:
|
|
83
|
+
source: expression.source,
|
|
80
84
|
context: context.full_name,
|
|
81
85
|
lines: options.fetch(:lines),
|
|
82
86
|
message: options.fetch(:message),
|
|
@@ -92,7 +96,7 @@ module Reek
|
|
|
92
96
|
[:def, :defs]
|
|
93
97
|
end
|
|
94
98
|
|
|
95
|
-
# :reek:UtilityFunction
|
|
99
|
+
# @quality :reek:UtilityFunction
|
|
96
100
|
def default_config
|
|
97
101
|
{
|
|
98
102
|
SmellConfiguration::ENABLED_KEY => true,
|
|
@@ -131,14 +135,14 @@ module Reek
|
|
|
131
135
|
# Note that we assume a valid name - exceptions are not handled here.
|
|
132
136
|
#
|
|
133
137
|
# @param detector_name [String] the detector in question, e.g. 'DuplicateMethodCall'
|
|
134
|
-
# @return [SmellDetector]
|
|
138
|
+
# @return [SmellDetector] this will return the class, not an instance
|
|
135
139
|
#
|
|
136
140
|
def to_detector(detector_name)
|
|
137
141
|
SmellDetectors.const_get detector_name
|
|
138
142
|
end
|
|
139
143
|
|
|
140
144
|
#
|
|
141
|
-
# @return [Set<Symbol>]
|
|
145
|
+
# @return [Set<Symbol>] all configuration keys that are available for this detector
|
|
142
146
|
#
|
|
143
147
|
def configuration_keys
|
|
144
148
|
Set.new(default_config.keys.map(&:to_sym))
|
|
@@ -19,14 +19,12 @@ module Reek
|
|
|
19
19
|
#
|
|
20
20
|
# @return [Array<SmellWarning>]
|
|
21
21
|
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
ctx.default_assignments.select do |_parameter, value|
|
|
22
|
+
def sniff
|
|
23
|
+
context.default_assignments.select do |_parameter, value|
|
|
25
24
|
[:true, :false].include?(value.type)
|
|
26
25
|
end.map do |parameter, _value|
|
|
27
26
|
smell_warning(
|
|
28
|
-
|
|
29
|
-
lines: [ctx.exp.line],
|
|
27
|
+
lines: [source_line],
|
|
30
28
|
message: "has boolean parameter '#{parameter}'",
|
|
31
29
|
parameters: { parameter: parameter.to_s })
|
|
32
30
|
end
|
|
@@ -24,10 +24,10 @@ module Reek
|
|
|
24
24
|
#
|
|
25
25
|
# @return [Array<SmellWarning>]
|
|
26
26
|
#
|
|
27
|
-
def sniff
|
|
28
|
-
|
|
27
|
+
def sniff
|
|
28
|
+
class_variables_in_context.map do |variable, occurences|
|
|
29
|
+
lines = occurences.map(&:line)
|
|
29
30
|
smell_warning(
|
|
30
|
-
context: ctx,
|
|
31
31
|
lines: lines,
|
|
32
32
|
message: "declares the class variable '#{variable}'",
|
|
33
33
|
parameters: { name: variable.to_s })
|
|
@@ -38,17 +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
|
-
|
|
42
|
-
|
|
43
|
-
def class_variables_in(exp)
|
|
44
|
-
result = Hash.new { |hash, key| hash[key] = [] }
|
|
45
|
-
collector = proc do |cvar_node|
|
|
46
|
-
result[cvar_node.name].push(cvar_node.line)
|
|
47
|
-
end
|
|
48
|
-
[:cvar, :cvasgn, :cvdecl].each do |stmt_type|
|
|
49
|
-
exp.each_node(stmt_type, [:class, :module], &collector)
|
|
50
|
-
end
|
|
51
|
-
result
|
|
41
|
+
def class_variables_in_context
|
|
42
|
+
context.local_nodes([:cvar, :cvasgn, :cvdecl]).group_by(&:name)
|
|
52
43
|
end
|
|
53
44
|
end
|
|
54
45
|
end
|
|
@@ -49,18 +49,32 @@ module Reek
|
|
|
49
49
|
#
|
|
50
50
|
# @return [Array<SmellWarning>]
|
|
51
51
|
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ControlParameterCollector.new(ctx).control_parameters.map do |control_parameter|
|
|
52
|
+
def sniff
|
|
53
|
+
control_parameters.map do |control_parameter|
|
|
55
54
|
argument = control_parameter.name.to_s
|
|
56
55
|
smell_warning(
|
|
57
|
-
context: ctx,
|
|
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:
|
|
@@ -50,14 +50,10 @@ module Reek
|
|
|
50
50
|
#
|
|
51
51
|
# @return [Array<SmellWarning>]
|
|
52
52
|
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
max_copies = value(MAX_COPIES_KEY, ctx)
|
|
56
|
-
min_clump_size = value(MIN_CLUMP_SIZE_KEY, ctx)
|
|
57
|
-
MethodGroup.new(ctx, min_clump_size, max_copies).clumps.map do |clump, methods|
|
|
53
|
+
def sniff
|
|
54
|
+
MethodGroup.new(context, min_clump_size, max_copies).clumps.map do |clump, methods|
|
|
58
55
|
methods_length = methods.length
|
|
59
56
|
smell_warning(
|
|
60
|
-
context: ctx,
|
|
61
57
|
lines: methods.map(&:line),
|
|
62
58
|
message: "takes parameters #{DataClump.print_clump(clump)} " \
|
|
63
59
|
"to #{methods_length} methods",
|
|
@@ -72,6 +68,16 @@ module Reek
|
|
|
72
68
|
def self.print_clump(clump)
|
|
73
69
|
"[#{clump.map { |parameter| "'#{parameter}'" }.join(', ')}]"
|
|
74
70
|
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def max_copies
|
|
75
|
+
value(MAX_COPIES_KEY, context)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def min_clump_size
|
|
79
|
+
value(MIN_CLUMP_SIZE_KEY, context)
|
|
80
|
+
end
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
83
|
|
|
@@ -94,7 +100,7 @@ module Reek
|
|
|
94
100
|
end.uniq
|
|
95
101
|
end
|
|
96
102
|
|
|
97
|
-
# :reek:UtilityFunction
|
|
103
|
+
# @quality :reek:UtilityFunction
|
|
98
104
|
def common_argument_names_for(methods)
|
|
99
105
|
methods.map(&:arg_names).inject(:&)
|
|
100
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
|
|
@@ -37,26 +37,32 @@ module Reek
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
#
|
|
40
|
-
# Looks for duplicate calls within the body of the method
|
|
40
|
+
# Looks for duplicate calls within the body of the method context.
|
|
41
41
|
#
|
|
42
42
|
# @return [Array<SmellWarning>]
|
|
43
43
|
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def sniff(ctx)
|
|
47
|
-
max_allowed_calls = value(MAX_ALLOWED_CALLS_KEY, ctx)
|
|
48
|
-
allow_calls = value(ALLOW_CALLS_KEY, ctx)
|
|
49
|
-
|
|
50
|
-
collector = CallCollector.new(ctx, max_allowed_calls, allow_calls)
|
|
44
|
+
def sniff
|
|
45
|
+
collector = CallCollector.new(context, max_allowed_calls, allow_calls)
|
|
51
46
|
collector.smelly_calls.map do |found_call|
|
|
47
|
+
call = found_call.call
|
|
48
|
+
occurs = found_call.occurs
|
|
52
49
|
smell_warning(
|
|
53
|
-
context: ctx,
|
|
54
50
|
lines: found_call.lines,
|
|
55
|
-
message: "calls '#{
|
|
56
|
-
parameters: { name:
|
|
51
|
+
message: "calls '#{call}' #{occurs} times",
|
|
52
|
+
parameters: { name: call, count: occurs })
|
|
57
53
|
end
|
|
58
54
|
end
|
|
59
55
|
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def max_allowed_calls
|
|
59
|
+
value(MAX_ALLOWED_CALLS_KEY, context)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def allow_calls
|
|
63
|
+
value(ALLOW_CALLS_KEY, context)
|
|
64
|
+
end
|
|
65
|
+
|
|
60
66
|
# Collects information about a single found call
|
|
61
67
|
class FoundCall
|
|
62
68
|
def initialize(call_node)
|
|
@@ -111,10 +117,10 @@ module Reek
|
|
|
111
117
|
|
|
112
118
|
attr_reader :allow_calls, :max_allowed_calls
|
|
113
119
|
|
|
114
|
-
# :reek:TooManyStatements
|
|
115
|
-
# :reek:DuplicateMethodCall
|
|
120
|
+
# @quality :reek:TooManyStatements { max_statements: 6 }
|
|
121
|
+
# @quality :reek:DuplicateMethodCall { max_calls: 2 }
|
|
116
122
|
def collect_calls(result)
|
|
117
|
-
context.
|
|
123
|
+
context.local_nodes(:send, [:mlhs]) do |call_node|
|
|
118
124
|
next if call_node.object_creation_call?
|
|
119
125
|
next if simple_method_call? call_node
|
|
120
126
|
result[call_node].record(call_node)
|
|
@@ -128,7 +134,7 @@ module Reek
|
|
|
128
134
|
found_call.occurs > max_allowed_calls && !allow_calls?(found_call.call)
|
|
129
135
|
end
|
|
130
136
|
|
|
131
|
-
# :reek:UtilityFunction
|
|
137
|
+
# @quality :reek:UtilityFunction
|
|
132
138
|
def simple_method_call?(call_node)
|
|
133
139
|
!call_node.receiver && call_node.args.empty?
|
|
134
140
|
end
|
|
@@ -42,11 +42,11 @@ module Reek
|
|
|
42
42
|
#
|
|
43
43
|
# @return [Array<SmellWarning>]
|
|
44
44
|
#
|
|
45
|
-
def sniff
|
|
46
|
-
return []
|
|
47
|
-
|
|
45
|
+
def sniff
|
|
46
|
+
return [] if context.singleton_method? || context.module_function?
|
|
47
|
+
return [] unless context.references_self?
|
|
48
|
+
envious_receivers.map do |name, lines|
|
|
48
49
|
smell_warning(
|
|
49
|
-
context: ctx,
|
|
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 })
|
|
@@ -55,9 +55,11 @@ module Reek
|
|
|
55
55
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
def refs
|
|
59
|
+
@refs ||= context.refs
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def envious_receivers
|
|
61
63
|
return {} if refs.self_is_max?
|
|
62
64
|
refs.most_popular
|
|
63
65
|
end
|
|
@@ -19,32 +19,31 @@ module Reek
|
|
|
19
19
|
#
|
|
20
20
|
# @return [Array<SmellWarning>]
|
|
21
21
|
#
|
|
22
|
-
def sniff
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
assumptions = (variables_from_context(method_expressions) -
|
|
26
|
-
variables_from_initialize(method_expressions)).uniq
|
|
22
|
+
def sniff
|
|
23
|
+
assumptions = (variables_from_context - variables_from_initialize).uniq
|
|
27
24
|
|
|
28
25
|
assumptions.map do |assumption|
|
|
29
|
-
build_smell_warning(
|
|
26
|
+
build_smell_warning(assumption)
|
|
30
27
|
end
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
private
|
|
34
31
|
|
|
35
|
-
def
|
|
32
|
+
def method_expressions
|
|
33
|
+
@method_expressions ||= context.node_instance_methods
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_smell_warning(assumption)
|
|
36
37
|
message = "assumes too much for instance variable '#{assumption}'"
|
|
37
38
|
|
|
38
39
|
smell_warning(
|
|
39
|
-
|
|
40
|
-
lines: [ctx.exp.line],
|
|
40
|
+
lines: [source_line],
|
|
41
41
|
message: message,
|
|
42
42
|
parameters: { assumption: assumption.to_s })
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
initialize_exp = instance_methods.detect do |method|
|
|
45
|
+
def variables_from_initialize
|
|
46
|
+
initialize_exp = method_expressions.detect do |method|
|
|
48
47
|
method.name == :initialize
|
|
49
48
|
end
|
|
50
49
|
|
|
@@ -53,19 +52,11 @@ module Reek
|
|
|
53
52
|
initialize_exp.each_node(:ivasgn).map(&:name)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
def variables_from_context
|
|
57
|
-
|
|
58
|
-
method.
|
|
55
|
+
def variables_from_context
|
|
56
|
+
method_expressions.map do |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
|
|
@@ -19,24 +19,17 @@ module Reek
|
|
|
19
19
|
#
|
|
20
20
|
# @return [Array<SmellWarning>]
|
|
21
21
|
#
|
|
22
|
-
def sniff
|
|
23
|
-
return [] if
|
|
24
|
-
expression = ctx.exp
|
|
22
|
+
def sniff
|
|
23
|
+
return [] if descriptive_context? || context.namespace_module?
|
|
25
24
|
[smell_warning(
|
|
26
|
-
|
|
27
|
-
lines: [expression.line],
|
|
25
|
+
lines: [source_line],
|
|
28
26
|
message: 'has no descriptive comment')]
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
private
|
|
32
30
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# :reek:FeatureEnvy
|
|
38
|
-
def descriptive?(ctx)
|
|
39
|
-
descriptive[ctx.full_name] ||= ctx.descriptively_commented?
|
|
31
|
+
def descriptive_context?
|
|
32
|
+
context.descriptively_commented?
|
|
40
33
|
end
|
|
41
34
|
end
|
|
42
35
|
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
|
|
@@ -32,17 +32,20 @@ module Reek
|
|
|
32
32
|
#
|
|
33
33
|
# @return [Array<SmellWarning>]
|
|
34
34
|
#
|
|
35
|
-
def sniff
|
|
36
|
-
|
|
37
|
-
exp = ctx.exp
|
|
38
|
-
count = exp.arg_names.length
|
|
35
|
+
def sniff
|
|
36
|
+
count = expression.arg_names.length
|
|
39
37
|
return [] if count <= max_allowed_params
|
|
40
38
|
[smell_warning(
|
|
41
|
-
|
|
42
|
-
lines: [exp.line],
|
|
39
|
+
lines: [source_line],
|
|
43
40
|
message: "has #{count} parameters",
|
|
44
41
|
parameters: { count: count })]
|
|
45
42
|
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def max_allowed_params
|
|
47
|
+
value(MAX_ALLOWED_PARAMS_KEY, context)
|
|
48
|
+
end
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
51
|
end
|
|
@@ -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,21 +24,24 @@ module Reek
|
|
|
24
24
|
#
|
|
25
25
|
# @return [Array<SmellWarning>]
|
|
26
26
|
#
|
|
27
|
-
# :reek:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx)
|
|
31
|
-
ctx.local_nodes(:yield).select do |yield_node|
|
|
27
|
+
# @quality :reek:DuplicateMethodCall { max_calls: 2 }
|
|
28
|
+
def sniff
|
|
29
|
+
context.local_nodes(:yield).select do |yield_node|
|
|
32
30
|
yield_node.args.length > max_allowed_params
|
|
33
31
|
end.map do |yield_node|
|
|
34
32
|
count = yield_node.args.length
|
|
35
33
|
smell_warning(
|
|
36
|
-
context: ctx,
|
|
37
34
|
lines: [yield_node.line],
|
|
38
35
|
message: "yields #{count} parameters",
|
|
39
36
|
parameters: { count: count })
|
|
40
37
|
end
|
|
41
38
|
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def max_allowed_params
|
|
43
|
+
value(MAX_ALLOWED_PARAMS_KEY, context)
|
|
44
|
+
end
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
end
|
|
@@ -12,19 +12,18 @@ 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
|
|
19
19
|
#
|
|
20
20
|
# @return [Array<SmellWarning>]
|
|
21
21
|
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
smelly_nodes = ctx.each_node(:send).select { |node| node.name == :respond_to? }
|
|
22
|
+
def sniff
|
|
23
|
+
smelly_nodes = context.local_nodes(:send).select { |node| node.name == :respond_to? }
|
|
25
24
|
return [] if smelly_nodes.empty?
|
|
26
25
|
lines = smelly_nodes.map(&:line)
|
|
27
|
-
[smell_warning(
|
|
26
|
+
[smell_warning(lines: lines, message: MESSAGE)]
|
|
28
27
|
end
|
|
29
28
|
end
|
|
30
29
|
end
|