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
|
@@ -21,16 +21,15 @@ 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
|
|
31
31
|
|
|
32
32
|
#
|
|
33
|
-
# @param ctx [Context::ModuleContext]
|
|
34
33
|
# @return [Array<SmellWarning>]
|
|
35
34
|
#
|
|
36
35
|
# Given this code:
|
|
@@ -40,38 +39,36 @@ module Reek
|
|
|
40
39
|
# end
|
|
41
40
|
# end
|
|
42
41
|
#
|
|
43
|
-
# An example
|
|
42
|
+
# An example context could look like this:
|
|
44
43
|
#
|
|
45
44
|
# s(:class,
|
|
46
45
|
# s(:const, nil, :Alfa), nil,
|
|
47
46
|
# s(:def, :bravo!,
|
|
48
47
|
# s(:args), nil))
|
|
49
48
|
#
|
|
50
|
-
def sniff
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
def sniff
|
|
50
|
+
context.node_instance_methods.select do |method_sexp|
|
|
51
|
+
missing_safe_method?(method_sexp)
|
|
53
52
|
end.map do |method_sexp|
|
|
54
53
|
name = method_sexp.name
|
|
55
54
|
smell_warning(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
message: "has prima donna method '#{name}'",
|
|
55
|
+
lines: [method_sexp.line],
|
|
56
|
+
message: "has missing safe method '#{name}'",
|
|
59
57
|
parameters: { name: name.to_s })
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
|
|
63
61
|
private
|
|
64
62
|
|
|
65
|
-
def
|
|
63
|
+
def missing_safe_method?(method_sexp)
|
|
66
64
|
return false unless method_sexp.ends_with_bang?
|
|
67
|
-
return false if ignore_method?
|
|
68
|
-
return false if version_without_bang_exists?
|
|
65
|
+
return false if ignore_method? method_sexp
|
|
66
|
+
return false if version_without_bang_exists? method_sexp
|
|
69
67
|
true
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
ctx.node_instance_methods.find do |sexp_item|
|
|
70
|
+
def version_without_bang_exists?(method_sexp)
|
|
71
|
+
context.node_instance_methods.find do |sexp_item|
|
|
75
72
|
sexp_item.name.to_s == method_sexp.name_without_bang
|
|
76
73
|
end
|
|
77
74
|
end
|
|
@@ -79,13 +76,16 @@ module Reek
|
|
|
79
76
|
#
|
|
80
77
|
# @param method_node [Reek::AST::Node],
|
|
81
78
|
# e.g. s(:def, :bravo!, s(:args), nil)
|
|
82
|
-
# @param ctx [Context::ModuleContext]
|
|
83
79
|
# @return [Boolean]
|
|
84
80
|
#
|
|
85
|
-
def ignore_method?(method_node
|
|
86
|
-
ignore_method_names = value(EXCLUDE_KEY, ctx) # e.g. ["bravo!"]
|
|
81
|
+
def ignore_method?(method_node)
|
|
87
82
|
ignore_method_names.include? method_node.name.to_s # method_node.name is e.g.: :bravo!
|
|
88
83
|
end
|
|
84
|
+
|
|
85
|
+
# e.g. ["bravo!"]
|
|
86
|
+
def ignore_method_names
|
|
87
|
+
@ignore_method_names ||= value(EXCLUDE_KEY, context)
|
|
88
|
+
end
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
end
|
|
@@ -20,13 +20,11 @@ module Reek
|
|
|
20
20
|
#
|
|
21
21
|
# @return [Array<SmellWarning>]
|
|
22
22
|
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
ctx.local_nodes(:def) do |node|
|
|
23
|
+
def sniff
|
|
24
|
+
context.defined_instance_methods.each do |node|
|
|
26
25
|
if node.name == :initialize
|
|
27
26
|
return smell_warning(
|
|
28
|
-
|
|
29
|
-
lines: [ctx.exp.line],
|
|
27
|
+
lines: [source_line],
|
|
30
28
|
message: 'has initialize method')
|
|
31
29
|
end
|
|
32
30
|
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
|
|
@@ -41,14 +41,10 @@ module Reek
|
|
|
41
41
|
#
|
|
42
42
|
# @return [Array<SmellWarning>]
|
|
43
43
|
#
|
|
44
|
-
def sniff
|
|
45
|
-
|
|
46
|
-
violations = find_violations ctx
|
|
47
|
-
|
|
48
|
-
violations.group_by(&:depth).map do |depth, group|
|
|
44
|
+
def sniff
|
|
45
|
+
find_violations.group_by(&:depth).map do |depth, group|
|
|
49
46
|
lines = group.map(&:line)
|
|
50
47
|
smell_warning(
|
|
51
|
-
context: ctx,
|
|
52
48
|
lines: lines,
|
|
53
49
|
message: "contains iterators nested #{depth} deep",
|
|
54
50
|
parameters: { depth: depth })
|
|
@@ -57,8 +53,6 @@ module Reek
|
|
|
57
53
|
|
|
58
54
|
private
|
|
59
55
|
|
|
60
|
-
attr_accessor :ignore_iterators
|
|
61
|
-
|
|
62
56
|
# Finds the set of independent most deeply nested iterators that are
|
|
63
57
|
# nested more deeply than allowed.
|
|
64
58
|
#
|
|
@@ -69,10 +63,8 @@ module Reek
|
|
|
69
63
|
#
|
|
70
64
|
# @return [Array<Iterator>]
|
|
71
65
|
#
|
|
72
|
-
def find_violations
|
|
73
|
-
|
|
74
|
-
max_allowed_nesting = max_nesting(ctx)
|
|
75
|
-
candidates.select { |it| it.depth > max_allowed_nesting }
|
|
66
|
+
def find_violations
|
|
67
|
+
find_candidates.select { |it| it.depth > max_allowed_nesting }
|
|
76
68
|
end
|
|
77
69
|
|
|
78
70
|
# Finds the set of independent most deeply nested iterators regardless of
|
|
@@ -80,9 +72,8 @@ module Reek
|
|
|
80
72
|
#
|
|
81
73
|
# @return [Array<Iterator>]
|
|
82
74
|
#
|
|
83
|
-
def find_candidates
|
|
84
|
-
exp
|
|
85
|
-
scout(exp: exp, depth: 0)
|
|
75
|
+
def find_candidates
|
|
76
|
+
scout(exp: expression, depth: 0)
|
|
86
77
|
end
|
|
87
78
|
|
|
88
79
|
# A little digression into parser's sexp is necessary here:
|
|
@@ -102,10 +93,11 @@ module Reek
|
|
|
102
93
|
#
|
|
103
94
|
# @return [Array<Iterator>]
|
|
104
95
|
#
|
|
105
|
-
# :reek:TooManyStatements
|
|
96
|
+
# @quality :reek:TooManyStatements { max_statements: 6 }
|
|
106
97
|
def scout(exp:, depth:)
|
|
107
98
|
return [] unless exp
|
|
108
|
-
|
|
99
|
+
# Find all non-nested blocks in this expression
|
|
100
|
+
exp.each_node([:block], [:block]).flat_map do |iterator|
|
|
109
101
|
new_depth = increment_depth(iterator, depth)
|
|
110
102
|
# 1st case: we recurse down the given block of the iterator. In this case
|
|
111
103
|
# we need to check if we should increment the depth.
|
|
@@ -121,19 +113,19 @@ module Reek
|
|
|
121
113
|
end
|
|
122
114
|
end
|
|
123
115
|
|
|
124
|
-
def
|
|
125
|
-
|
|
116
|
+
def ignore_iterators
|
|
117
|
+
@ignore_iterators ||= value(IGNORE_ITERATORS_KEY, context)
|
|
126
118
|
end
|
|
127
119
|
|
|
128
120
|
def increment_depth(iterator, depth)
|
|
129
121
|
ignored_iterator?(iterator) ? depth : depth + 1
|
|
130
122
|
end
|
|
131
123
|
|
|
132
|
-
def
|
|
133
|
-
value(MAX_ALLOWED_NESTING_KEY,
|
|
124
|
+
def max_allowed_nesting
|
|
125
|
+
@max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context)
|
|
134
126
|
end
|
|
135
127
|
|
|
136
|
-
# :reek:FeatureEnvy
|
|
128
|
+
# @quality :reek:FeatureEnvy
|
|
137
129
|
def ignored_iterator?(exp)
|
|
138
130
|
ignore_iterators.any? { |pattern| /#{pattern}/ =~ exp.call.name } ||
|
|
139
131
|
exp.without_block_arguments?
|
|
@@ -9,11 +9,10 @@ module Reek
|
|
|
9
9
|
#
|
|
10
10
|
# See {file:docs/Nil-Check.md} for details.
|
|
11
11
|
class NilCheck < BaseDetector
|
|
12
|
-
def sniff
|
|
13
|
-
lines =
|
|
12
|
+
def sniff
|
|
13
|
+
lines = detect_nodes.map(&:line)
|
|
14
14
|
if lines.any?
|
|
15
15
|
[smell_warning(
|
|
16
|
-
context: ctx,
|
|
17
16
|
lines: lines,
|
|
18
17
|
message: 'performs a nil-check')]
|
|
19
18
|
else
|
|
@@ -21,19 +20,13 @@ module Reek
|
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
class NodeDetector
|
|
26
|
-
attr_reader :ctx
|
|
27
|
-
def initialize(ctx)
|
|
28
|
-
@ctx = ctx
|
|
29
|
-
end
|
|
23
|
+
private
|
|
30
24
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
25
|
+
def detect_nodes
|
|
26
|
+
finders = [NodeFinder.new(context, :send, NilCallNodeDetector),
|
|
27
|
+
NodeFinder.new(context, :when, NilWhenNodeDetector),
|
|
28
|
+
NodeFinder.new(context, :csend, SafeNavigationNodeDetector)]
|
|
29
|
+
finders.flat_map(&:smelly_nodes)
|
|
37
30
|
end
|
|
38
31
|
|
|
39
32
|
#
|
|
@@ -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,38 +46,40 @@ module Reek
|
|
|
46
46
|
#
|
|
47
47
|
# @return [Array<SmellWarning>]
|
|
48
48
|
#
|
|
49
|
-
# :reek:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx)
|
|
53
|
-
conditional_counts(ctx).select do |_key, lines|
|
|
49
|
+
# @quality :reek:DuplicateMethodCall { max_calls: 2 }
|
|
50
|
+
def sniff
|
|
51
|
+
conditional_counts.select do |_key, lines|
|
|
54
52
|
lines.length > max_identical_ifs
|
|
55
53
|
end.map do |key, lines|
|
|
56
54
|
occurs = lines.length
|
|
57
55
|
expression = key.format_to_ruby
|
|
58
56
|
smell_warning(
|
|
59
|
-
context: ctx,
|
|
60
57
|
lines: lines,
|
|
61
58
|
message: "tests '#{expression}' at least #{occurs} times",
|
|
62
59
|
parameters: { name: expression, count: occurs })
|
|
63
60
|
end
|
|
64
61
|
end
|
|
65
62
|
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def max_identical_ifs
|
|
66
|
+
@max_identical_ifs ||= value(MAX_IDENTICAL_IFS_KEY, context)
|
|
67
|
+
end
|
|
68
|
+
|
|
66
69
|
#
|
|
67
70
|
# Returns a Hash listing all of the conditional expressions in
|
|
68
71
|
# the given syntax tree together with the number of times each
|
|
69
72
|
# occurs. Ignores nested classes and modules.
|
|
70
73
|
#
|
|
71
|
-
# :reek:TooManyStatements
|
|
72
|
-
|
|
73
|
-
def conditional_counts(sexp)
|
|
74
|
+
# @quality :reek:TooManyStatements { max_statements: 9 }
|
|
75
|
+
def conditional_counts
|
|
74
76
|
result = Hash.new { |hash, key| hash[key] = [] }
|
|
75
77
|
collector = proc do |node|
|
|
76
78
|
next unless (condition = node.condition)
|
|
77
79
|
next if condition == BLOCK_GIVEN_CONDITION
|
|
78
80
|
result[condition].push(condition.line)
|
|
79
81
|
end
|
|
80
|
-
[:if, :case].each { |stmt|
|
|
82
|
+
[:if, :case].each { |stmt| context.local_nodes(stmt, &collector) }
|
|
81
83
|
result
|
|
82
84
|
end
|
|
83
85
|
end
|
|
@@ -26,26 +26,25 @@ module Reek
|
|
|
26
26
|
# class Foo < Bar; end;
|
|
27
27
|
#
|
|
28
28
|
# @return [Array<SmellWarning>]
|
|
29
|
-
def sniff
|
|
30
|
-
superclass =
|
|
29
|
+
def sniff
|
|
30
|
+
superclass = expression.superclass
|
|
31
31
|
|
|
32
32
|
return [] unless superclass
|
|
33
33
|
|
|
34
|
-
sniff_superclass
|
|
34
|
+
sniff_superclass superclass.name
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
private
|
|
38
38
|
|
|
39
|
-
def sniff_superclass(
|
|
39
|
+
def sniff_superclass(superclass_name)
|
|
40
40
|
return [] unless CORE_CLASSES.include?(superclass_name)
|
|
41
41
|
|
|
42
|
-
[build_smell_warning(
|
|
42
|
+
[build_smell_warning(superclass_name)]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def build_smell_warning(
|
|
45
|
+
def build_smell_warning(ancestor_name)
|
|
46
46
|
smell_attributes = {
|
|
47
|
-
|
|
48
|
-
lines: [ctx.exp.line],
|
|
47
|
+
lines: [source_line],
|
|
49
48
|
message: "inherits from core class '#{ancestor_name}'",
|
|
50
49
|
parameters: { ancestor: ancestor_name }
|
|
51
50
|
}
|
|
@@ -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
|
|
|
@@ -34,22 +34,23 @@ module Reek
|
|
|
34
34
|
#
|
|
35
35
|
# @return [Array<SmellWarning>]
|
|
36
36
|
#
|
|
37
|
-
def sniff
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
count = ctx.each_node(:casgn, IGNORED_NODES).delete_if(&:defines_module?).length
|
|
37
|
+
def sniff
|
|
38
|
+
count = context.local_nodes(:casgn).reject(&:defines_module?).length
|
|
41
39
|
|
|
42
40
|
return [] if count <= max_allowed_constants
|
|
43
41
|
|
|
44
|
-
build_smell_warning(
|
|
42
|
+
build_smell_warning(count)
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
private
|
|
48
46
|
|
|
49
|
-
def
|
|
47
|
+
def max_allowed_constants
|
|
48
|
+
value(MAX_ALLOWED_CONSTANTS_KEY, context)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def build_smell_warning(count)
|
|
50
52
|
[smell_warning(
|
|
51
|
-
|
|
52
|
-
lines: [ctx.exp.line],
|
|
53
|
+
lines: [source_line],
|
|
53
54
|
message: "has #{count} constants",
|
|
54
55
|
parameters: { count: count })]
|
|
55
56
|
end
|
|
@@ -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
|
|
@@ -33,17 +33,21 @@ module Reek
|
|
|
33
33
|
#
|
|
34
34
|
# @return [Array<SmellWarning>]
|
|
35
35
|
#
|
|
36
|
-
def sniff
|
|
37
|
-
|
|
38
|
-
variables = ctx.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
|
|
36
|
+
def sniff
|
|
37
|
+
variables = context.local_nodes(:ivasgn, [:or_asgn]).map(&:name)
|
|
39
38
|
count = variables.uniq.size
|
|
40
39
|
return [] if count <= max_allowed_ivars
|
|
41
40
|
[smell_warning(
|
|
42
|
-
|
|
43
|
-
lines: [ctx.exp.line],
|
|
41
|
+
lines: [source_line],
|
|
44
42
|
message: "has at least #{count} instance variables",
|
|
45
43
|
parameters: { count: count })]
|
|
46
44
|
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def max_allowed_ivars
|
|
49
|
+
value(MAX_ALLOWED_IVARS_KEY, context)
|
|
50
|
+
end
|
|
47
51
|
end
|
|
48
52
|
end
|
|
49
53
|
end
|
|
@@ -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
|
|
@@ -31,21 +31,25 @@ module Reek
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
#
|
|
34
|
-
# Checks
|
|
34
|
+
# Checks context for too many methods
|
|
35
35
|
#
|
|
36
36
|
# @return [Array<SmellWarning>]
|
|
37
37
|
#
|
|
38
|
-
def sniff
|
|
39
|
-
max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx)
|
|
38
|
+
def sniff
|
|
40
39
|
# TODO: Only checks instance methods!
|
|
41
|
-
actual =
|
|
40
|
+
actual = context.node_instance_methods.length
|
|
42
41
|
return [] if actual <= max_allowed_methods
|
|
43
42
|
[smell_warning(
|
|
44
|
-
|
|
45
|
-
lines: [ctx.exp.line],
|
|
43
|
+
lines: [source_line],
|
|
46
44
|
message: "has at least #{actual} methods",
|
|
47
45
|
parameters: { count: actual })]
|
|
48
46
|
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def max_allowed_methods
|
|
51
|
+
value(MAX_ALLOWED_METHODS_KEY, context)
|
|
52
|
+
end
|
|
49
53
|
end
|
|
50
54
|
end
|
|
51
55
|
end
|
|
@@ -13,7 +13,7 @@ module Reek
|
|
|
13
13
|
class TooManyStatements < BaseDetector
|
|
14
14
|
# The name of the config field that sets the maximum number of
|
|
15
15
|
# statements permitted in any method.
|
|
16
|
-
MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
|
|
16
|
+
MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
|
|
17
17
|
DEFAULT_MAX_STATEMENTS = 5
|
|
18
18
|
|
|
19
19
|
def self.default_config
|
|
@@ -27,16 +27,20 @@ module Reek
|
|
|
27
27
|
#
|
|
28
28
|
# @return [Array<SmellWarning>]
|
|
29
29
|
#
|
|
30
|
-
def sniff
|
|
31
|
-
|
|
32
|
-
count = ctx.number_of_statements
|
|
30
|
+
def sniff
|
|
31
|
+
count = context.number_of_statements
|
|
33
32
|
return [] if count <= max_allowed_statements
|
|
34
33
|
[smell_warning(
|
|
35
|
-
|
|
36
|
-
lines: [ctx.exp.line],
|
|
34
|
+
lines: [source_line],
|
|
37
35
|
message: "has approx #{count} statements",
|
|
38
36
|
parameters: { count: count })]
|
|
39
37
|
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def max_allowed_statements
|
|
42
|
+
value(MAX_ALLOWED_STATEMENTS_KEY, context)
|
|
43
|
+
end
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
end
|
|
@@ -20,8 +20,8 @@ module Reek
|
|
|
20
20
|
#
|
|
21
21
|
# See {file:docs/Uncommunicative-Method-Name.md} for details.
|
|
22
22
|
class UncommunicativeMethodName < BaseDetector
|
|
23
|
-
REJECT_KEY = 'reject'
|
|
24
|
-
ACCEPT_KEY = 'accept'
|
|
23
|
+
REJECT_KEY = 'reject'
|
|
24
|
+
ACCEPT_KEY = 'accept'
|
|
25
25
|
DEFAULT_REJECT_PATTERNS = [/^[a-z]$/, /[0-9]$/, /[A-Z]/].freeze
|
|
26
26
|
DEFAULT_ACCEPT_PATTERNS = [].freeze
|
|
27
27
|
|
|
@@ -36,29 +36,28 @@ module Reek
|
|
|
36
36
|
#
|
|
37
37
|
# @return [Array<SmellWarning>]
|
|
38
38
|
#
|
|
39
|
-
def sniff
|
|
39
|
+
def sniff
|
|
40
40
|
name = context.name.to_s
|
|
41
|
-
return [] if acceptable_name?(name
|
|
41
|
+
return [] if acceptable_name?(name)
|
|
42
42
|
|
|
43
43
|
[smell_warning(
|
|
44
|
-
|
|
45
|
-
lines: [context.exp.line],
|
|
44
|
+
lines: [source_line],
|
|
46
45
|
message: "has the name '#{name}'",
|
|
47
46
|
parameters: { name: name })]
|
|
48
47
|
end
|
|
49
48
|
|
|
50
49
|
private
|
|
51
50
|
|
|
52
|
-
def acceptable_name?(name
|
|
53
|
-
accept_patterns
|
|
54
|
-
reject_patterns
|
|
51
|
+
def acceptable_name?(name)
|
|
52
|
+
accept_patterns.any? { |accept_pattern| name.match accept_pattern } ||
|
|
53
|
+
reject_patterns.none? { |reject_pattern| name.match reject_pattern }
|
|
55
54
|
end
|
|
56
55
|
|
|
57
|
-
def reject_patterns
|
|
56
|
+
def reject_patterns
|
|
58
57
|
Array value(REJECT_KEY, context)
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
def accept_patterns
|
|
60
|
+
def accept_patterns
|
|
62
61
|
Array value(ACCEPT_KEY, context)
|
|
63
62
|
end
|
|
64
63
|
end
|
|
@@ -21,13 +21,13 @@ module Reek
|
|
|
21
21
|
class UncommunicativeModuleName < BaseDetector
|
|
22
22
|
# The name of the config field that lists the regexps of
|
|
23
23
|
# smelly names to be reported.
|
|
24
|
-
REJECT_KEY = 'reject'
|
|
24
|
+
REJECT_KEY = 'reject'
|
|
25
25
|
DEFAULT_REJECT_PATTERNS = [/^.$/, /[0-9]$/].freeze
|
|
26
26
|
|
|
27
27
|
# The name of the config field that lists the specific names that are
|
|
28
28
|
# to be treated as exceptions; these names will not be reported as
|
|
29
29
|
# uncommunicative.
|
|
30
|
-
ACCEPT_KEY = 'accept'
|
|
30
|
+
ACCEPT_KEY = 'accept'
|
|
31
31
|
DEFAULT_ACCEPT_PATTERNS = [].freeze
|
|
32
32
|
|
|
33
33
|
def self.default_config
|
|
@@ -41,40 +41,36 @@ module Reek
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
#
|
|
44
|
-
# Checks the
|
|
44
|
+
# Checks the detector's context for uncommunicative names.
|
|
45
45
|
#
|
|
46
46
|
# @return [Array<SmellWarning>]
|
|
47
47
|
#
|
|
48
|
-
def sniff
|
|
48
|
+
def sniff
|
|
49
49
|
fully_qualified_name = context.full_name
|
|
50
|
-
|
|
51
|
-
module_name = exp.simple_name
|
|
50
|
+
module_name = expression.simple_name
|
|
52
51
|
|
|
53
|
-
return [] if acceptable_name?(
|
|
54
|
-
module_name: module_name,
|
|
52
|
+
return [] if acceptable_name?(module_name: module_name,
|
|
55
53
|
fully_qualified_name: fully_qualified_name)
|
|
56
54
|
|
|
57
55
|
[smell_warning(
|
|
58
|
-
|
|
59
|
-
lines: [exp.line],
|
|
56
|
+
lines: [source_line],
|
|
60
57
|
message: "has the name '#{module_name}'",
|
|
61
58
|
parameters: { name: module_name })]
|
|
62
59
|
end
|
|
63
60
|
|
|
64
61
|
private
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
reject_patterns(context).none? { |reject_pattern| module_name.match reject_pattern }
|
|
63
|
+
def acceptable_name?(module_name:, fully_qualified_name:)
|
|
64
|
+
accept_patterns.any? { |accept_pattern| fully_qualified_name.match accept_pattern } ||
|
|
65
|
+
reject_patterns.none? { |reject_pattern| module_name.match reject_pattern }
|
|
70
66
|
end
|
|
71
67
|
|
|
72
|
-
def reject_patterns
|
|
73
|
-
Array value(REJECT_KEY, context)
|
|
68
|
+
def reject_patterns
|
|
69
|
+
@reject_patterns ||= Array value(REJECT_KEY, context)
|
|
74
70
|
end
|
|
75
71
|
|
|
76
|
-
def accept_patterns
|
|
77
|
-
Array value(ACCEPT_KEY, context)
|
|
72
|
+
def accept_patterns
|
|
73
|
+
@accept_patterns ||= Array value(ACCEPT_KEY, context)
|
|
78
74
|
end
|
|
79
75
|
end
|
|
80
76
|
end
|