reek 1.2.6 → 1.2.8
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.
- data/.yardopts +10 -0
- data/History.txt +62 -0
- data/README.md +74 -0
- data/bin/reek +2 -2
- data/config/defaults.reek +44 -5
- data/features/api.feature +20 -0
- data/features/masking_smells.feature +60 -42
- data/features/options.feature +7 -2
- data/features/rake_task.feature +29 -22
- data/features/reports.feature +9 -41
- data/features/samples.feature +184 -196
- data/features/stdin.feature +5 -8
- data/features/step_definitions/reek_steps.rb +8 -4
- data/features/support/env.rb +2 -3
- data/features/yaml.feature +85 -0
- data/lib/reek/cli/application.rb +46 -0
- data/lib/reek/cli/command_line.rb +108 -0
- data/lib/reek/cli/help_command.rb +18 -0
- data/lib/reek/cli/reek_command.rb +37 -0
- data/lib/reek/cli/report.rb +58 -0
- data/lib/reek/cli/version_command.rb +19 -0
- data/lib/reek/cli/yaml_command.rb +32 -0
- data/lib/reek/core/code_context.rb +64 -0
- data/lib/reek/core/code_parser.rb +166 -0
- data/lib/reek/core/method_context.rb +84 -0
- data/lib/reek/core/module_context.rb +20 -0
- data/lib/reek/core/object_refs.rb +51 -0
- data/lib/reek/core/singleton_method_context.rb +20 -0
- data/lib/reek/core/smell_configuration.rb +62 -0
- data/lib/reek/core/sniffer.rb +105 -0
- data/lib/reek/core/stop_context.rb +30 -0
- data/lib/reek/core/warning_collector.rb +27 -0
- data/lib/reek/examiner.rb +104 -0
- data/lib/reek/rake/task.rb +142 -0
- data/lib/reek/smell_warning.rb +73 -23
- data/lib/reek/smells/attribute.rb +26 -21
- data/lib/reek/smells/boolean_parameter.rb +38 -0
- data/lib/reek/smells/class_variable.rb +22 -9
- data/lib/reek/smells/control_couple.rb +40 -16
- data/lib/reek/smells/data_clump.rb +114 -23
- data/lib/reek/smells/duplication.rb +55 -15
- data/lib/reek/smells/feature_envy.rb +16 -7
- data/lib/reek/smells/irresponsible_module.rb +37 -0
- data/lib/reek/smells/large_class.rb +37 -19
- data/lib/reek/smells/long_method.rb +20 -9
- data/lib/reek/smells/long_parameter_list.rb +22 -10
- data/lib/reek/smells/long_yield_list.rb +43 -7
- data/lib/reek/smells/nested_iterators.rb +68 -8
- data/lib/reek/smells/simulated_polymorphism.rb +26 -15
- data/lib/reek/smells/smell_detector.rb +29 -51
- data/lib/reek/smells/uncommunicative_method_name.rb +74 -0
- data/lib/reek/smells/uncommunicative_module_name.rb +74 -0
- data/lib/reek/smells/uncommunicative_parameter_name.rb +79 -0
- data/lib/reek/smells/uncommunicative_variable_name.rb +89 -0
- data/lib/reek/smells/utility_function.rb +51 -12
- data/lib/reek/smells.rb +29 -0
- data/lib/reek/source/code_comment.rb +37 -0
- data/lib/reek/source/config_file.rb +72 -0
- data/lib/reek/source/core_extras.rb +46 -0
- data/lib/reek/source/reference_collector.rb +28 -0
- data/lib/reek/source/sexp_formatter.rb +17 -0
- data/lib/reek/source/source_code.rb +44 -0
- data/lib/reek/source/source_file.rb +32 -0
- data/lib/reek/source/source_locator.rb +42 -0
- data/lib/reek/source/tree_dresser.rb +204 -0
- data/lib/reek/source.rb +18 -0
- data/lib/reek/spec/should_reek.rb +31 -0
- data/lib/reek/spec/should_reek_of.rb +37 -0
- data/lib/reek/spec/should_reek_only_of.rb +37 -0
- data/lib/reek/spec.rb +51 -0
- data/lib/reek.rb +8 -4
- data/reek.gemspec +9 -7
- data/spec/matchers/smell_of_matcher.rb +58 -0
- data/spec/reek/{help_command_spec.rb → cli/help_command_spec.rb} +3 -4
- data/spec/reek/cli/reek_command_spec.rb +46 -0
- data/spec/reek/cli/report_spec.rb +30 -0
- data/spec/reek/{version_command_spec.rb → cli/version_command_spec.rb} +3 -3
- data/spec/reek/cli/yaml_command_spec.rb +47 -0
- data/spec/reek/core/code_context_spec.rb +145 -0
- data/spec/reek/{code_parser_spec.rb → core/code_parser_spec.rb} +7 -6
- data/spec/reek/{config_spec.rb → core/config_spec.rb} +2 -6
- data/spec/reek/{method_context_spec.rb → core/method_context_spec.rb} +16 -36
- data/spec/reek/core/module_context_spec.rb +27 -0
- data/spec/reek/{object_refs_spec.rb → core/object_refs_spec.rb} +5 -6
- data/spec/reek/core/singleton_method_context_spec.rb +9 -0
- data/spec/reek/core/smell_configuration_spec.rb +11 -0
- data/spec/reek/core/stop_context_spec.rb +17 -0
- data/spec/reek/core/warning_collector_spec.rb +27 -0
- data/spec/reek/examiner_spec.rb +103 -0
- data/spec/reek/smell_warning_spec.rb +74 -82
- data/spec/reek/smells/attribute_spec.rb +42 -23
- data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
- data/spec/reek/smells/boolean_parameter_spec.rb +66 -0
- data/spec/reek/smells/class_variable_spec.rb +60 -75
- data/spec/reek/smells/control_couple_spec.rb +37 -32
- data/spec/reek/smells/data_clump_spec.rb +63 -24
- data/spec/reek/smells/duplication_spec.rb +106 -57
- data/spec/reek/smells/feature_envy_spec.rb +113 -92
- data/spec/reek/smells/irresponsible_module_spec.rb +58 -0
- data/spec/reek/smells/large_class_spec.rb +67 -57
- data/spec/reek/smells/long_method_spec.rb +40 -10
- data/spec/reek/smells/long_parameter_list_spec.rb +50 -28
- data/spec/reek/smells/long_yield_list_spec.rb +57 -0
- data/spec/reek/smells/nested_iterators_spec.rb +115 -7
- data/spec/reek/smells/simulated_polymorphism_spec.rb +56 -20
- data/spec/reek/smells/smell_detector_shared.rb +42 -0
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +42 -0
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +66 -0
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +71 -0
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +120 -0
- data/spec/reek/smells/utility_function_spec.rb +50 -4
- data/spec/reek/source/code_comment_spec.rb +82 -0
- data/spec/reek/source/object_source_spec.rb +20 -0
- data/spec/reek/source/reference_collector_spec.rb +53 -0
- data/spec/reek/{adapters/source_spec.rb → source/source_code_spec.rb} +7 -8
- data/spec/reek/source/tree_dresser_spec.rb +270 -0
- data/spec/reek/spec/should_reek_of_spec.rb +76 -0
- data/spec/reek/spec/should_reek_only_of_spec.rb +89 -0
- data/spec/reek/{adapters → spec}/should_reek_spec.rb +8 -32
- data/spec/samples/all_but_one_masked/clean_one.rb +1 -0
- data/spec/samples/all_but_one_masked/dirty.rb +1 -0
- data/spec/samples/all_but_one_masked/masked.reek +5 -1
- data/spec/samples/clean_due_to_masking/clean_one.rb +1 -0
- data/spec/samples/clean_due_to_masking/clean_three.rb +1 -0
- data/spec/samples/clean_due_to_masking/clean_two.rb +1 -0
- data/spec/samples/clean_due_to_masking/dirty_one.rb +1 -1
- data/spec/samples/clean_due_to_masking/dirty_two.rb +1 -1
- data/spec/samples/clean_due_to_masking/masked.reek +5 -1
- data/spec/samples/config/allow_duplication.reek +3 -0
- data/spec/samples/config/deeper_nested_iterators.reek +3 -0
- data/spec/samples/corrupt_config_file/dirty.rb +1 -1
- data/spec/samples/demo/demo.rb +8 -0
- data/spec/samples/empty_config_file/dirty.rb +2 -1
- data/spec/samples/exceptions.reek +1 -1
- data/spec/samples/inline_config/dirty.rb +16 -0
- data/spec/samples/inline_config/masked.reek +7 -0
- data/spec/samples/mask_some/dirty.rb +8 -0
- data/spec/samples/mask_some/some.reek +8 -0
- data/spec/samples/masked/dirty.rb +2 -1
- data/spec/samples/masked/masked.reek +3 -1
- data/spec/samples/mixed_results/clean_one.rb +1 -0
- data/spec/samples/mixed_results/clean_three.rb +1 -0
- data/spec/samples/mixed_results/clean_two.rb +1 -0
- data/spec/samples/mixed_results/dirty_one.rb +1 -0
- data/spec/samples/mixed_results/dirty_two.rb +1 -0
- data/spec/samples/not_quite_masked/dirty.rb +2 -1
- data/spec/samples/not_quite_masked/masked.reek +1 -1
- data/spec/samples/overrides/masked/dirty.rb +2 -1
- data/spec/samples/overrides/masked/lower.reek +3 -1
- data/spec/samples/three_clean_files/clean_one.rb +1 -0
- data/spec/samples/three_clean_files/clean_three.rb +1 -0
- data/spec/samples/three_clean_files/clean_two.rb +1 -0
- data/spec/samples/two_smelly_files/dirty_one.rb +2 -1
- data/spec/samples/two_smelly_files/dirty_two.rb +2 -1
- data/spec/spec_helper.rb +10 -2
- data/tasks/reek.rake +2 -2
- data/tasks/test.rake +12 -3
- metadata +121 -79
- data/README.rdoc +0 -84
- data/features/profile.feature +0 -34
- data/lib/reek/adapters/application.rb +0 -46
- data/lib/reek/adapters/command_line.rb +0 -77
- data/lib/reek/adapters/config_file.rb +0 -31
- data/lib/reek/adapters/core_extras.rb +0 -64
- data/lib/reek/adapters/rake_task.rb +0 -121
- data/lib/reek/adapters/report.rb +0 -86
- data/lib/reek/adapters/source.rb +0 -72
- data/lib/reek/adapters/spec.rb +0 -133
- data/lib/reek/block_context.rb +0 -62
- data/lib/reek/class_context.rb +0 -41
- data/lib/reek/code_context.rb +0 -68
- data/lib/reek/code_parser.rb +0 -203
- data/lib/reek/configuration.rb +0 -57
- data/lib/reek/detector_stack.rb +0 -37
- data/lib/reek/help_command.rb +0 -14
- data/lib/reek/if_context.rb +0 -18
- data/lib/reek/masking_collection.rb +0 -33
- data/lib/reek/method_context.rb +0 -138
- data/lib/reek/module_context.rb +0 -49
- data/lib/reek/name.rb +0 -57
- data/lib/reek/object_refs.rb +0 -49
- data/lib/reek/reek_command.rb +0 -28
- data/lib/reek/sexp_formatter.rb +0 -10
- data/lib/reek/singleton_method_context.rb +0 -26
- data/lib/reek/smells/uncommunicative_name.rb +0 -84
- data/lib/reek/sniffer.rb +0 -177
- data/lib/reek/stop_context.rb +0 -35
- data/lib/reek/tree_dresser.rb +0 -82
- data/lib/reek/version_command.rb +0 -14
- data/lib/reek/yield_call_context.rb +0 -12
- data/spec/reek/adapters/report_spec.rb +0 -31
- data/spec/reek/adapters/should_reek_of_spec.rb +0 -138
- data/spec/reek/adapters/should_reek_only_of_spec.rb +0 -87
- data/spec/reek/block_context_spec.rb +0 -65
- data/spec/reek/class_context_spec.rb +0 -161
- data/spec/reek/code_context_spec.rb +0 -182
- data/spec/reek/configuration_spec.rb +0 -12
- data/spec/reek/if_context_spec.rb +0 -17
- data/spec/reek/masking_collection_spec.rb +0 -236
- data/spec/reek/module_context_spec.rb +0 -46
- data/spec/reek/name_spec.rb +0 -37
- data/spec/reek/object_source_spec.rb +0 -23
- data/spec/reek/reek_command_spec.rb +0 -45
- data/spec/reek/singleton_method_context_spec.rb +0 -16
- data/spec/reek/smells/smell_detector_spec.rb +0 -36
- data/spec/reek/smells/uncommunicative_name_spec.rb +0 -146
- data/spec/reek/sniffer_spec.rb +0 -11
- data/spec/reek/stop_context_spec.rb +0 -33
- data/spec/reek/tree_dresser_spec.rb +0 -20
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
3
|
-
require 'reek/sexp_formatter'
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
4
3
|
|
|
5
4
|
module Reek
|
|
6
5
|
module Smells
|
|
@@ -35,14 +34,24 @@ module Reek
|
|
|
35
34
|
class FeatureEnvy < SmellDetector
|
|
36
35
|
include ExcludeInitialize
|
|
37
36
|
|
|
37
|
+
SMELL_CLASS = 'LowCohesion'
|
|
38
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
39
|
+
|
|
40
|
+
RECEIVER_KEY = 'receiver'
|
|
41
|
+
REFERENCES_KEY = 'references'
|
|
42
|
+
|
|
38
43
|
#
|
|
39
44
|
# Checks whether the given +context+ includes any code fragment that
|
|
40
45
|
# might "belong" on another class.
|
|
41
|
-
# Remembers any smells found.
|
|
42
46
|
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
# @return [Array<SmellWarning>]
|
|
48
|
+
#
|
|
49
|
+
def examine_context(method_ctx)
|
|
50
|
+
method_ctx.envious_receivers.map do |ref, occurs|
|
|
51
|
+
target = ref.format_ruby
|
|
52
|
+
SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
|
53
|
+
"refers to #{target} more than self",
|
|
54
|
+
@source, SMELL_SUBCLASS, {RECEIVER_KEY => target, REFERENCES_KEY => occurs})
|
|
46
55
|
end
|
|
47
56
|
end
|
|
48
57
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Smells
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# It is considered good practice to annotate every class and module
|
|
9
|
+
# with a brief comment outlining its responsibilities.
|
|
10
|
+
#
|
|
11
|
+
class IrresponsibleModule < SmellDetector
|
|
12
|
+
|
|
13
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
|
14
|
+
SMELL_SUBCLASS = SMELL_CLASS
|
|
15
|
+
|
|
16
|
+
MODULE_NAME_KEY = 'module_name'
|
|
17
|
+
|
|
18
|
+
def self.contexts # :nodoc:
|
|
19
|
+
[:class]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# Checks the given class or module for a descriptive comment.
|
|
24
|
+
#
|
|
25
|
+
# @return [Array<SmellWarning>]
|
|
26
|
+
#
|
|
27
|
+
def examine_context(ctx)
|
|
28
|
+
comment = Source::CodeComment.new(ctx.exp.comments)
|
|
29
|
+
return [] if comment.is_descriptive?
|
|
30
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
31
|
+
'has no descriptive comment',
|
|
32
|
+
@source, SMELL_SUBCLASS, {MODULE_NAME_KEY => ctx.exp.text_name})
|
|
33
|
+
[smell]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
3
|
|
|
4
4
|
module Reek
|
|
5
5
|
module Smells
|
|
@@ -16,6 +16,12 @@ module Reek
|
|
|
16
16
|
#
|
|
17
17
|
class LargeClass < SmellDetector
|
|
18
18
|
|
|
19
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
|
20
|
+
SUBCLASS_TOO_MANY_METHODS = 'TooManyMethods'
|
|
21
|
+
SUBCLASS_TOO_MANY_IVARS = 'TooManyInstanceVariables'
|
|
22
|
+
METHOD_COUNT_KEY = 'method_count'
|
|
23
|
+
IVAR_COUNT_KEY = 'ivar_count'
|
|
24
|
+
|
|
19
25
|
# The name of the config field that sets the maximum number of methods
|
|
20
26
|
# permitted in a class.
|
|
21
27
|
MAX_ALLOWED_METHODS_KEY = 'max_methods'
|
|
@@ -40,29 +46,41 @@ module Reek
|
|
|
40
46
|
)
|
|
41
47
|
end
|
|
42
48
|
|
|
43
|
-
def initialize(config = LargeClass.default_config)
|
|
44
|
-
super(config)
|
|
49
|
+
def initialize(source, config = LargeClass.default_config)
|
|
50
|
+
super(source, config)
|
|
45
51
|
end
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
#
|
|
54
|
+
# Checks +klass+ for too many methods or too many instance variables.
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<SmellWarning>]
|
|
57
|
+
#
|
|
58
|
+
def examine_context(ctx)
|
|
59
|
+
@max_allowed_ivars = value(MAX_ALLOWED_IVARS_KEY, ctx, DEFAULT_MAX_IVARS)
|
|
60
|
+
@max_allowed_methods = value(MAX_ALLOWED_METHODS_KEY, ctx, DEFAULT_MAX_METHODS)
|
|
61
|
+
check_num_methods(ctx) + check_num_ivars(ctx)
|
|
51
62
|
end
|
|
52
63
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def check_num_methods(ctx) # :nodoc:
|
|
67
|
+
actual = ctx.local_nodes(:defn).length
|
|
68
|
+
return [] if actual <= @max_allowed_methods
|
|
69
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
70
|
+
"has at least #{actual} methods",
|
|
71
|
+
@source, SUBCLASS_TOO_MANY_METHODS,
|
|
72
|
+
{METHOD_COUNT_KEY => actual})
|
|
73
|
+
[smell]
|
|
57
74
|
end
|
|
58
75
|
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
def check_num_ivars(ctx) # :nodoc:
|
|
77
|
+
count = ctx.local_nodes(:iasgn).map {|iasgn| iasgn[1]}.uniq.length
|
|
78
|
+
return [] if count <= @max_allowed_ivars
|
|
79
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
80
|
+
"has at least #{count} instance variables",
|
|
81
|
+
@source, SUBCLASS_TOO_MANY_IVARS,
|
|
82
|
+
{IVAR_COUNT_KEY => count})
|
|
83
|
+
[smell]
|
|
66
84
|
end
|
|
67
85
|
end
|
|
68
86
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
3
|
|
|
4
4
|
module Reek
|
|
5
5
|
module Smells
|
|
@@ -12,6 +12,11 @@ module Reek
|
|
|
12
12
|
#
|
|
13
13
|
class LongMethod < SmellDetector
|
|
14
14
|
|
|
15
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
|
16
|
+
SUBCLASS_TOO_MANY_STATEMENTS = 'TooManyStatements'
|
|
17
|
+
|
|
18
|
+
STATEMENT_COUNT_KEY = 'statement_count'
|
|
19
|
+
|
|
15
20
|
# The name of the config field that sets the maximum number of
|
|
16
21
|
# statements permitted in any method.
|
|
17
22
|
MAX_ALLOWED_STATEMENTS_KEY = 'max_statements'
|
|
@@ -25,18 +30,24 @@ module Reek
|
|
|
25
30
|
)
|
|
26
31
|
end
|
|
27
32
|
|
|
28
|
-
def initialize(config = LongMethod.default_config)
|
|
29
|
-
super(config)
|
|
33
|
+
def initialize(source, config = LongMethod.default_config)
|
|
34
|
+
super(source, config)
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
#
|
|
33
38
|
# Checks the length of the given +method+.
|
|
34
|
-
# Remembers any smells found.
|
|
35
39
|
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
# @return [Array<SmellWarning>]
|
|
41
|
+
#
|
|
42
|
+
def examine_context(ctx)
|
|
43
|
+
@max_allowed_statements = value(MAX_ALLOWED_STATEMENTS_KEY, ctx, DEFAULT_MAX_STATEMENTS)
|
|
44
|
+
num = ctx.num_statements
|
|
45
|
+
return [] if num <= @max_allowed_statements
|
|
46
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
47
|
+
"has approx #{num} statements",
|
|
48
|
+
@source, SUBCLASS_TOO_MANY_STATEMENTS,
|
|
49
|
+
{STATEMENT_COUNT_KEY => num})
|
|
50
|
+
[smell]
|
|
40
51
|
end
|
|
41
52
|
end
|
|
42
53
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'core', 'smell_configuration')
|
|
2
4
|
|
|
3
5
|
module Reek
|
|
4
6
|
module Smells
|
|
@@ -13,6 +15,11 @@ module Reek
|
|
|
13
15
|
#
|
|
14
16
|
class LongParameterList < SmellDetector
|
|
15
17
|
|
|
18
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
|
19
|
+
SMELL_SUBCLASS = 'LongParameterList'
|
|
20
|
+
|
|
21
|
+
PARAMETER_COUNT_KEY = 'parameter_count'
|
|
22
|
+
|
|
16
23
|
# The name of the config field that sets the maximum number of
|
|
17
24
|
# parameters permitted in any method or block.
|
|
18
25
|
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
|
@@ -24,25 +31,30 @@ module Reek
|
|
|
24
31
|
def self.default_config
|
|
25
32
|
super.adopt(
|
|
26
33
|
MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS,
|
|
27
|
-
SmellConfiguration::OVERRIDES_KEY => {
|
|
34
|
+
Core::SmellConfiguration::OVERRIDES_KEY => {
|
|
28
35
|
"initialize" => {MAX_ALLOWED_PARAMS_KEY => 5}
|
|
29
36
|
}
|
|
30
37
|
)
|
|
31
38
|
end
|
|
32
39
|
|
|
33
|
-
def initialize(config = LongParameterList.default_config)
|
|
34
|
-
super(config)
|
|
35
|
-
@action = 'has'
|
|
40
|
+
def initialize(source, config = LongParameterList.default_config)
|
|
41
|
+
super(source, config)
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
#
|
|
39
|
-
# Checks the number of parameters in the given
|
|
40
|
-
#
|
|
45
|
+
# Checks the number of parameters in the given method.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<SmellWarning>]
|
|
41
48
|
#
|
|
42
49
|
def examine_context(ctx)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
50
|
+
@max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, ctx, DEFAULT_MAX_ALLOWED_PARAMS)
|
|
51
|
+
num_params = ctx.exp.arg_names.length
|
|
52
|
+
return [] if num_params <= @max_allowed_params
|
|
53
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
54
|
+
"has #{num_params} parameters",
|
|
55
|
+
@source, SMELL_SUBCLASS,
|
|
56
|
+
{PARAMETER_COUNT_KEY => num_params})
|
|
57
|
+
[smell]
|
|
46
58
|
end
|
|
47
59
|
end
|
|
48
60
|
end
|
|
@@ -1,17 +1,53 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
2
3
|
|
|
3
4
|
module Reek
|
|
4
5
|
module Smells
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
#
|
|
8
|
+
# A variant on LongParameterList that checks the number of items
|
|
9
|
+
# passed to a block by a +yield+ call.
|
|
10
|
+
#
|
|
11
|
+
class LongYieldList < SmellDetector
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
14
|
+
SMELL_CLASS = 'LongParameterList'
|
|
15
|
+
|
|
16
|
+
# The name of the config field that sets the maximum number of
|
|
17
|
+
# parameters permitted in any method or block.
|
|
18
|
+
MAX_ALLOWED_PARAMS_KEY = 'max_params'
|
|
19
|
+
|
|
20
|
+
# The default value of the +MAX_ALLOWED_PARAMS_KEY+ configuration
|
|
21
|
+
# value.
|
|
22
|
+
DEFAULT_MAX_ALLOWED_PARAMS = 3
|
|
23
|
+
|
|
24
|
+
PARAMETER_COUNT_KEY = 'parameter_count'
|
|
25
|
+
|
|
26
|
+
def self.default_config
|
|
27
|
+
super.adopt(
|
|
28
|
+
MAX_ALLOWED_PARAMS_KEY => DEFAULT_MAX_ALLOWED_PARAMS
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize(source, config = LongYieldList.default_config)
|
|
33
|
+
super(source, config)
|
|
10
34
|
end
|
|
11
35
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
36
|
+
#
|
|
37
|
+
# Checks the number of parameters in the given scope.
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<SmellWarning>]
|
|
40
|
+
#
|
|
41
|
+
def examine_context(method_ctx)
|
|
42
|
+
@max_allowed_params = value(MAX_ALLOWED_PARAMS_KEY, method_ctx, DEFAULT_MAX_ALLOWED_PARAMS)
|
|
43
|
+
method_ctx.local_nodes(:yield).select do |yield_node|
|
|
44
|
+
yield_node.args.length > @max_allowed_params
|
|
45
|
+
end.map do |yield_node|
|
|
46
|
+
num_params = yield_node.args.length
|
|
47
|
+
SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [yield_node.line],
|
|
48
|
+
"yields #{num_params} parameters",
|
|
49
|
+
@source, SMELL_SUBCLASS, {PARAMETER_COUNT_KEY => num_params})
|
|
50
|
+
end
|
|
15
51
|
end
|
|
16
52
|
end
|
|
17
53
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
3
|
|
|
4
4
|
module Reek
|
|
5
5
|
module Smells
|
|
@@ -11,17 +11,77 @@ module Reek
|
|
|
11
11
|
#
|
|
12
12
|
class NestedIterators < SmellDetector
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
|
15
|
+
SMELL_SUBCLASS = SMELL_CLASS
|
|
16
|
+
# SMELL: should be a subclass of UnnecessaryComplexity
|
|
17
|
+
NESTING_DEPTH_KEY = 'depth'
|
|
18
|
+
|
|
19
|
+
# The name of the config field that sets the maximum depth
|
|
20
|
+
# of nested iterators to be permitted within any single method.
|
|
21
|
+
MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'
|
|
22
|
+
|
|
23
|
+
DEFAULT_MAX_ALLOWED_NESTING = 1
|
|
24
|
+
|
|
25
|
+
# The name of the config field that sets the names of any
|
|
26
|
+
# methods for which nesting should not be considered
|
|
27
|
+
IGNORE_ITERATORS_KEY = 'ignore_iterators'
|
|
28
|
+
|
|
29
|
+
DEFAULT_IGNORE_ITERATORS = []
|
|
30
|
+
|
|
31
|
+
def self.default_config
|
|
32
|
+
super.adopt(
|
|
33
|
+
MAX_ALLOWED_NESTING_KEY => DEFAULT_MAX_ALLOWED_NESTING,
|
|
34
|
+
IGNORE_ITERATORS_KEY => DEFAULT_IGNORE_ITERATORS
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def initialize(source, config = NestedIterators.default_config)
|
|
39
|
+
super(source, config)
|
|
16
40
|
end
|
|
17
41
|
|
|
18
42
|
#
|
|
19
43
|
# Checks whether the given +block+ is inside another.
|
|
20
|
-
# Remembers any smells found.
|
|
21
44
|
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
45
|
+
# @return [Array<SmellWarning>]
|
|
46
|
+
#
|
|
47
|
+
def examine_context(ctx)
|
|
48
|
+
@ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
|
|
49
|
+
@max_allowed_nesting = value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
|
|
50
|
+
find_deepest_iterators(ctx).map do |iter|
|
|
51
|
+
depth = iter[1]
|
|
52
|
+
SmellWarning.new(SMELL_CLASS, ctx.full_name, [iter[0].line],
|
|
53
|
+
"contains iterators nested #{depth} deep",
|
|
54
|
+
@source, SMELL_SUBCLASS,
|
|
55
|
+
{NESTING_DEPTH_KEY => depth})
|
|
56
|
+
end
|
|
57
|
+
# BUG: no longer reports nesting outside methods (eg. in Optparse)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def find_deepest_iterators(ctx)
|
|
63
|
+
result = []
|
|
64
|
+
find_iters(ctx.exp, 1, result)
|
|
65
|
+
result.select {|item| item[1] > @max_allowed_nesting}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def find_iters(exp, depth, result)
|
|
69
|
+
exp.each do |elem|
|
|
70
|
+
next unless Sexp === elem
|
|
71
|
+
case elem.first
|
|
72
|
+
when :iter
|
|
73
|
+
find_iters([elem.call], depth, result)
|
|
74
|
+
current = result.length
|
|
75
|
+
call = Source::SexpFormatter.format(elem.call)
|
|
76
|
+
ignored = @ignore_iterators.any? { |ignore| /#{ignore}/ === call }
|
|
77
|
+
find_iters([elem.block], depth + (ignored ? 0 : 1), result)
|
|
78
|
+
result << [elem, depth] if result.length == current unless ignored
|
|
79
|
+
when :class, :defn, :defs, :module
|
|
80
|
+
next
|
|
81
|
+
else
|
|
82
|
+
find_iters(elem, depth, result)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
25
85
|
end
|
|
26
86
|
end
|
|
27
87
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
3
|
-
require 'reek/sexp_formatter'
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
4
3
|
|
|
5
4
|
module Reek
|
|
6
5
|
module Smells
|
|
@@ -24,6 +23,9 @@ module Reek
|
|
|
24
23
|
#
|
|
25
24
|
class SimulatedPolymorphism < SmellDetector
|
|
26
25
|
|
|
26
|
+
SMELL_CLASS = self.name.split(/::/)[-1]
|
|
27
|
+
SMELL_SUBCLASS = 'RepeatedConditional'
|
|
28
|
+
|
|
27
29
|
def self.contexts # :nodoc:
|
|
28
30
|
[:class]
|
|
29
31
|
end
|
|
@@ -38,17 +40,26 @@ module Reek
|
|
|
38
40
|
super.adopt(MAX_IDENTICAL_IFS_KEY => DEFAULT_MAX_IFS)
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
def initialize(config = SimulatedPolymorphism.default_config)
|
|
42
|
-
super(config)
|
|
43
|
+
def initialize(source, config = SimulatedPolymorphism.default_config)
|
|
44
|
+
super(source, config)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
#
|
|
46
|
-
# Checks the given
|
|
47
|
-
#
|
|
48
|
+
# Checks the given class for multiple identical conditional tests.
|
|
49
|
+
#
|
|
50
|
+
# @return [Array<SmellWarning>]
|
|
48
51
|
#
|
|
49
|
-
def examine_context(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
def examine_context(ctx)
|
|
53
|
+
@max_identical_ifs = value(MAX_IDENTICAL_IFS_KEY, ctx, DEFAULT_MAX_IFS)
|
|
54
|
+
conditional_counts(ctx).select do |key, lines|
|
|
55
|
+
lines.length > @max_identical_ifs
|
|
56
|
+
end.map do |key, lines|
|
|
57
|
+
occurs = lines.length
|
|
58
|
+
expr = key.format_ruby
|
|
59
|
+
SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
|
|
60
|
+
"tests #{expr} at least #{occurs} times",
|
|
61
|
+
@source, SMELL_SUBCLASS,
|
|
62
|
+
{'expression' => expr, 'occurrences' => occurs})
|
|
52
63
|
end
|
|
53
64
|
end
|
|
54
65
|
|
|
@@ -57,14 +68,14 @@ module Reek
|
|
|
57
68
|
# the given syntax tree together with the number of times each
|
|
58
69
|
# occurs. Ignores nested classes and modules.
|
|
59
70
|
#
|
|
60
|
-
def conditional_counts(
|
|
61
|
-
result = Hash.new
|
|
71
|
+
def conditional_counts(sexp)
|
|
72
|
+
result = Hash.new {|hash, key| hash[key] = []}
|
|
62
73
|
collector = proc { |node|
|
|
63
74
|
condition = node.condition
|
|
64
|
-
|
|
75
|
+
next if condition.nil? or condition == s(:call, nil, :block_given?, s(:arglist))
|
|
76
|
+
result[condition].push(condition.line)
|
|
65
77
|
}
|
|
66
|
-
|
|
67
|
-
klass.local_nodes(:case, &collector)
|
|
78
|
+
[:if, :case].each {|stmt| sexp.local_nodes(stmt, &collector) }
|
|
68
79
|
result
|
|
69
80
|
end
|
|
70
81
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'set'
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'core', 'smell_configuration')
|
|
2
4
|
|
|
3
5
|
module Reek
|
|
4
6
|
module Smells
|
|
@@ -7,11 +9,14 @@ module Reek
|
|
|
7
9
|
def self.default_config
|
|
8
10
|
super.adopt(EXCLUDE_KEY => ['initialize'])
|
|
9
11
|
end
|
|
10
|
-
def initialize(config = self.class.default_config)
|
|
11
|
-
super
|
|
12
|
+
def initialize(source, config = self.class.default_config)
|
|
13
|
+
super(source, config)
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
#
|
|
18
|
+
# Shared responsibilities of all smell detectors.
|
|
19
|
+
#
|
|
15
20
|
class SmellDetector
|
|
16
21
|
|
|
17
22
|
# The name of the config field that lists the names of code contexts
|
|
@@ -24,25 +29,28 @@ module Reek
|
|
|
24
29
|
DEFAULT_EXCLUDE_SET = []
|
|
25
30
|
|
|
26
31
|
class << self
|
|
27
|
-
def contexts
|
|
32
|
+
def contexts
|
|
28
33
|
[:defn, :defs]
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def default_config
|
|
32
37
|
{
|
|
33
|
-
SmellConfiguration::ENABLED_KEY => true,
|
|
38
|
+
Core::SmellConfiguration::ENABLED_KEY => true,
|
|
34
39
|
EXCLUDE_KEY => DEFAULT_EXCLUDE_SET
|
|
35
40
|
}
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@
|
|
44
|
+
attr_reader :smells_found # SMELL: only published for tests
|
|
45
|
+
|
|
46
|
+
def initialize(source, config = self.class.default_config)
|
|
47
|
+
@source = source
|
|
48
|
+
@config = Core::SmellConfiguration.new(config)
|
|
49
|
+
@smells_found = []
|
|
43
50
|
end
|
|
44
51
|
|
|
45
|
-
def
|
|
52
|
+
def register(hooks)
|
|
53
|
+
return unless @config.enabled?
|
|
46
54
|
self.class.contexts.each { |ctx| hooks[ctx] << self }
|
|
47
55
|
end
|
|
48
56
|
|
|
@@ -51,66 +59,36 @@ module Reek
|
|
|
51
59
|
@config.enabled?
|
|
52
60
|
end
|
|
53
61
|
|
|
54
|
-
def configure(config)
|
|
55
|
-
my_part = config[self.class.name.split(/::/)[-1]]
|
|
56
|
-
return unless my_part
|
|
57
|
-
configure_with(my_part)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
62
|
def configure_with(config)
|
|
61
63
|
@config.adopt!(config)
|
|
62
64
|
end
|
|
63
65
|
|
|
64
|
-
def copy
|
|
65
|
-
self.class.new(@config.deep_copy)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def supersede_with(config)
|
|
69
|
-
clone = self.copy
|
|
70
|
-
@masked = true
|
|
71
|
-
clone.configure_with(config)
|
|
72
|
-
clone
|
|
73
|
-
end
|
|
74
|
-
|
|
75
66
|
def examine(context)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
enabled = @config.enabled? && config_for(context)[Core::SmellConfiguration::ENABLED_KEY] != false
|
|
68
|
+
if enabled && !exception?(context)
|
|
69
|
+
sm = examine_context(context)
|
|
70
|
+
@smells_found += sm
|
|
71
|
+
end
|
|
79
72
|
end
|
|
80
73
|
|
|
81
74
|
def examine_context(context)
|
|
82
75
|
end
|
|
83
|
-
|
|
76
|
+
|
|
84
77
|
def exception?(context)
|
|
85
78
|
context.matches?(value(EXCLUDE_KEY, context, DEFAULT_EXCLUDE_SET))
|
|
86
79
|
end
|
|
87
80
|
|
|
88
|
-
def found(context, message)
|
|
89
|
-
smell = SmellWarning.new(self, context.full_name, context.exp.line, message, @masked)
|
|
90
|
-
@smells_found << smell
|
|
91
|
-
smell
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def has_smell?(patterns)
|
|
95
|
-
return false if @masked
|
|
96
|
-
@smells_found.each { |warning| return true if warning.contains_all?(patterns) }
|
|
97
|
-
false
|
|
98
|
-
end
|
|
99
|
-
|
|
100
81
|
def report_on(report)
|
|
101
82
|
@smells_found.each { |smell| smell.report_on(report) }
|
|
102
83
|
end
|
|
103
84
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def smelly?
|
|
109
|
-
(not @masked) and (@smells_found.length > 0)
|
|
85
|
+
def value(key, ctx, fall_back)
|
|
86
|
+
config_for(ctx)[key] || @config.value(key, ctx, fall_back)
|
|
110
87
|
end
|
|
111
88
|
|
|
112
|
-
def
|
|
113
|
-
|
|
89
|
+
def config_for(ctx)
|
|
90
|
+
ctx.config[self.class.name.split(/::/)[-1]] || {}
|
|
91
|
+
# BUG: needs to consider smell class AND subclass
|
|
114
92
|
end
|
|
115
93
|
end
|
|
116
94
|
end
|