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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_context')
|
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'object_refs')
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Core
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# The parameters in a method's definition.
|
|
9
|
+
#
|
|
10
|
+
module MethodParameters
|
|
11
|
+
def default_assignments
|
|
12
|
+
assignments = self[-1]
|
|
13
|
+
result = []
|
|
14
|
+
return result unless is_assignment_block?(assignments)
|
|
15
|
+
assignments[1..-1].each do |exp|
|
|
16
|
+
result << exp[1..2] if exp[0] == :lasgn
|
|
17
|
+
end
|
|
18
|
+
result
|
|
19
|
+
end
|
|
20
|
+
def is_arg?(param)
|
|
21
|
+
return false if is_assignment_block?(param)
|
|
22
|
+
return !(param.to_s =~ /^\&/)
|
|
23
|
+
end
|
|
24
|
+
def is_assignment_block?(param)
|
|
25
|
+
Array === param and param[0] == :block
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def names
|
|
29
|
+
return @names if @names
|
|
30
|
+
@names = self[1..-1].select {|arg| is_arg?(arg)}.map {|arg| arg.to_s }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def length
|
|
34
|
+
names.length
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def include?(name)
|
|
38
|
+
names.include?(name)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# A context wrapper for any method definition found in a syntax tree.
|
|
44
|
+
#
|
|
45
|
+
class MethodContext < CodeContext
|
|
46
|
+
attr_reader :parameters
|
|
47
|
+
attr_reader :refs
|
|
48
|
+
attr_reader :num_statements
|
|
49
|
+
|
|
50
|
+
def initialize(outer, exp)
|
|
51
|
+
super(outer, exp)
|
|
52
|
+
@parameters = exp[exp[0] == :defn ? 2 : 3] # SMELL: SimulatedPolymorphism
|
|
53
|
+
@parameters ||= []
|
|
54
|
+
@parameters.extend(MethodParameters)
|
|
55
|
+
@num_statements = 0
|
|
56
|
+
@refs = ObjectRefs.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def count_statements(num)
|
|
60
|
+
@num_statements += num
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def record_call_to(exp)
|
|
64
|
+
receiver, meth = exp[1..2]
|
|
65
|
+
receiver ||= [:self]
|
|
66
|
+
case receiver[0]
|
|
67
|
+
when :lvar
|
|
68
|
+
@refs.record_ref(receiver) unless meth == :new
|
|
69
|
+
when :self
|
|
70
|
+
record_use_of_self
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def record_use_of_self
|
|
75
|
+
@refs.record_reference_to_self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def envious_receivers
|
|
79
|
+
return [] if @refs.self_is_max?
|
|
80
|
+
@refs.max_keys
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_context')
|
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_parser')
|
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'sniffer')
|
|
4
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'sexp_formatter')
|
|
5
|
+
|
|
6
|
+
module Reek
|
|
7
|
+
module Core
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# A context wrapper for any module found in a syntax tree.
|
|
11
|
+
#
|
|
12
|
+
class ModuleContext < CodeContext
|
|
13
|
+
|
|
14
|
+
def initialize(outer, name, exp)
|
|
15
|
+
super(outer, exp)
|
|
16
|
+
@name = name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Reek
|
|
2
|
+
module Core
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Manages and counts the references out of a method to other objects.
|
|
6
|
+
#
|
|
7
|
+
class ObjectRefs # :nodoc:
|
|
8
|
+
def initialize
|
|
9
|
+
@refs = Hash.new(0)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def record_reference_to_self
|
|
13
|
+
record_ref(SELF_REF)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def record_ref(exp)
|
|
17
|
+
type = exp[0]
|
|
18
|
+
case type
|
|
19
|
+
when :gvar
|
|
20
|
+
return
|
|
21
|
+
when :self
|
|
22
|
+
record_reference_to_self
|
|
23
|
+
else
|
|
24
|
+
@refs[exp] += 1
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def refs_to_self
|
|
29
|
+
@refs[SELF_REF]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def max_refs
|
|
33
|
+
@refs.values.max or 0
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def max_keys
|
|
37
|
+
max = max_refs
|
|
38
|
+
@refs.reject {|key,val| val != max}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self_is_max?
|
|
42
|
+
max_keys.length == 0 || @refs[SELF_REF] == max_refs
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
SELF_REF = Sexp.from_array([:lit, :self])
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Core
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# A context wrapper for any singleton method definition found in a syntax tree.
|
|
8
|
+
#
|
|
9
|
+
class SingletonMethodContext < MethodContext
|
|
10
|
+
|
|
11
|
+
def initialize(outer, exp)
|
|
12
|
+
super(outer, exp)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def envious_receivers
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Reek
|
|
2
|
+
module Core
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Represents a single set of configuration options for a smell detector
|
|
6
|
+
#
|
|
7
|
+
class SmellConfiguration
|
|
8
|
+
|
|
9
|
+
# The name of the config field that specifies whether a smell is
|
|
10
|
+
# enabled. Set to +true+ or +false+.
|
|
11
|
+
ENABLED_KEY = 'enabled'
|
|
12
|
+
|
|
13
|
+
# The name of the config field that sets scope-specific overrides
|
|
14
|
+
# for other values in the current smell detector's configuration.
|
|
15
|
+
OVERRIDES_KEY = 'overrides'
|
|
16
|
+
|
|
17
|
+
def initialize(hash)
|
|
18
|
+
@options = hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def adopt!(options)
|
|
22
|
+
@options.adopt!(options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# Is this smell detector active?
|
|
27
|
+
#--
|
|
28
|
+
# SMELL: Getter
|
|
29
|
+
def enabled?
|
|
30
|
+
@options[ENABLED_KEY]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def overrides_for(context)
|
|
34
|
+
Overrides.new(@options.fetch(OVERRIDES_KEY, {})).for_context(context)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Retrieves the value, if any, for the given +key+.
|
|
38
|
+
#
|
|
39
|
+
# Returns +fall_back+ if this config has no value for the key.
|
|
40
|
+
#
|
|
41
|
+
def value(key, context, fall_back)
|
|
42
|
+
overrides_for(context).each { |conf| return conf[key] if conf.has_key?(key) }
|
|
43
|
+
return @options.fetch(key, fall_back)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
#
|
|
48
|
+
# A set of context-specific overrides for smell detectors.
|
|
49
|
+
#
|
|
50
|
+
class Overrides
|
|
51
|
+
def initialize(hash)
|
|
52
|
+
@hash = hash
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Find any overrides that match the supplied context
|
|
56
|
+
def for_context(context)
|
|
57
|
+
contexts = @hash.keys.select {|ckey| context.matches?([ckey])}
|
|
58
|
+
contexts.map { |exc| @hash[exc] }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'code_parser')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smells')
|
|
3
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'config_file')
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Extensions to +Hash+ needed by Reek.
|
|
8
|
+
#
|
|
9
|
+
class Hash
|
|
10
|
+
def push_keys(hash)
|
|
11
|
+
keys.each {|key| hash[key].adopt!(self[key]) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def adopt!(other)
|
|
15
|
+
other.keys.each do |key|
|
|
16
|
+
ov = other[key]
|
|
17
|
+
if Array === ov and has_key?(key)
|
|
18
|
+
self[key] += ov
|
|
19
|
+
else
|
|
20
|
+
self[key] = ov
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def adopt(other)
|
|
27
|
+
self.deep_copy.adopt!(other)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def deep_copy
|
|
31
|
+
YAML::load(YAML::dump(self))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Reek
|
|
36
|
+
module Core
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Configures all available smell detectors and applies them to a source.
|
|
40
|
+
#
|
|
41
|
+
class Sniffer
|
|
42
|
+
|
|
43
|
+
def self.smell_classes
|
|
44
|
+
# SMELL: Duplication -- these should be loaded by listing the files
|
|
45
|
+
[
|
|
46
|
+
Smells::Attribute,
|
|
47
|
+
Smells::BooleanParameter,
|
|
48
|
+
Smells::ClassVariable,
|
|
49
|
+
Smells::ControlCouple,
|
|
50
|
+
Smells::DataClump,
|
|
51
|
+
Smells::Duplication,
|
|
52
|
+
Smells::FeatureEnvy,
|
|
53
|
+
Smells::IrresponsibleModule,
|
|
54
|
+
Smells::LargeClass,
|
|
55
|
+
Smells::LongMethod,
|
|
56
|
+
Smells::LongParameterList,
|
|
57
|
+
Smells::LongYieldList,
|
|
58
|
+
Smells::NestedIterators,
|
|
59
|
+
Smells::SimulatedPolymorphism,
|
|
60
|
+
Smells::UncommunicativeMethodName,
|
|
61
|
+
Smells::UncommunicativeModuleName,
|
|
62
|
+
Smells::UncommunicativeParameterName,
|
|
63
|
+
Smells::UncommunicativeVariableName,
|
|
64
|
+
Smells::UtilityFunction,
|
|
65
|
+
]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def initialize(src, config_files = [])
|
|
69
|
+
@typed_detectors = nil
|
|
70
|
+
@detectors = Hash.new
|
|
71
|
+
Sniffer.smell_classes.each do |klass|
|
|
72
|
+
@detectors[klass] = klass.new(src.desc)
|
|
73
|
+
end
|
|
74
|
+
config_files.each{ |cf| Reek::Source::ConfigFile.new(cf).configure(self) }
|
|
75
|
+
@source = src
|
|
76
|
+
src.configure(self)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def configure(klass, config)
|
|
80
|
+
@detectors[klass].configure_with(config)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def report_on(listener)
|
|
84
|
+
CodeParser.new(self).process(@source.syntax_tree)
|
|
85
|
+
@detectors.each_value { |detector| detector.report_on(listener) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def examine(scope, node_type)
|
|
89
|
+
smell_listeners[node_type].each do |detector|
|
|
90
|
+
detector.examine(scope)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def smell_listeners()
|
|
97
|
+
unless @typed_detectors
|
|
98
|
+
@typed_detectors = Hash.new {|hash,key| hash[key] = [] }
|
|
99
|
+
@detectors.each_value { |detector| detector.register(@typed_detectors) }
|
|
100
|
+
end
|
|
101
|
+
@typed_detectors
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Reek
|
|
2
|
+
module Core
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# A context wrapper representing the root of an abstract syntax tree.
|
|
6
|
+
#
|
|
7
|
+
class StopContext
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@name = ''
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def method_missing(method, *args)
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def config
|
|
18
|
+
{}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def count_statements(num)
|
|
22
|
+
0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def full_name
|
|
26
|
+
''
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Core
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Collects and sorts smells warnings.
|
|
8
|
+
#
|
|
9
|
+
class WarningCollector
|
|
10
|
+
def initialize
|
|
11
|
+
@warnings = Set.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def found_smell(warning)
|
|
15
|
+
@warnings.add(warning)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def warnings
|
|
19
|
+
@warnings.to_a.sort do |first,second|
|
|
20
|
+
first_sig = [first.context, first.message, first.smell_class]
|
|
21
|
+
second_sig = [second.context, second.message, second.smell_class]
|
|
22
|
+
first_sig <=> second_sig
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'sniffer')
|
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'core', 'warning_collector')
|
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'source')
|
|
4
|
+
|
|
5
|
+
module Reek
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# Finds the active code smells in Ruby source code.
|
|
9
|
+
#
|
|
10
|
+
class Examiner
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# A simple description of the source being analysed for smells.
|
|
14
|
+
# If the source is a single File, this will be the file's path.
|
|
15
|
+
#
|
|
16
|
+
attr_accessor :description
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# Creates an Examiner which scans the given +source+ for code smells.
|
|
20
|
+
#
|
|
21
|
+
# The smells reported against any source file can be "masked" by
|
|
22
|
+
# creating *.reek files. See TBS for details.
|
|
23
|
+
#
|
|
24
|
+
# @param [Source::SourceCode, Array<String>, #to_reek_source]
|
|
25
|
+
# If +source+ is a String it is assumed to be Ruby source code;
|
|
26
|
+
# if it is a File, the file is opened and parsed for Ruby source code;
|
|
27
|
+
# and if it is an Array, it is assumed to be a list of file paths,
|
|
28
|
+
# each of which is opened and parsed for source code.
|
|
29
|
+
#
|
|
30
|
+
def initialize(source, config_files = [])
|
|
31
|
+
sources = case source
|
|
32
|
+
when Array
|
|
33
|
+
@description = 'dir'
|
|
34
|
+
Source::SourceLocator.new(source).all_sources
|
|
35
|
+
when Source::SourceCode
|
|
36
|
+
@description = source.desc
|
|
37
|
+
[source]
|
|
38
|
+
else
|
|
39
|
+
src = source.to_reek_source
|
|
40
|
+
@description = src.desc
|
|
41
|
+
[src]
|
|
42
|
+
end
|
|
43
|
+
collector = Core::WarningCollector.new
|
|
44
|
+
sources.each { |src| Core::Sniffer.new(src, config_files).report_on(collector) }
|
|
45
|
+
@smells = collector.warnings
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#
|
|
49
|
+
# List the smells found in the source.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<SmellWarning>]
|
|
52
|
+
#
|
|
53
|
+
def smells
|
|
54
|
+
@smells
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
#
|
|
58
|
+
# True if and only if there are code smells in the source.
|
|
59
|
+
#
|
|
60
|
+
def smelly?
|
|
61
|
+
not @smells.empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
#
|
|
65
|
+
# Returns an Array of SmellWarning objects, one for each non-masked smell
|
|
66
|
+
# in the source.
|
|
67
|
+
#
|
|
68
|
+
# @deprecated Use #smells instead.
|
|
69
|
+
#
|
|
70
|
+
def all_active_smells
|
|
71
|
+
@smells
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Returns an Array of SmellWarning objects, one for each smell
|
|
76
|
+
# in the source; includes active smells and masked smells.
|
|
77
|
+
#
|
|
78
|
+
# @return [Array<SmellWarning>]
|
|
79
|
+
#
|
|
80
|
+
# @deprecated Use #smells instead.
|
|
81
|
+
#
|
|
82
|
+
def all_smells
|
|
83
|
+
@smells
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# Returns the number of non-masked smells in the source.
|
|
88
|
+
#
|
|
89
|
+
# @deprecated Use #smells instead.
|
|
90
|
+
#
|
|
91
|
+
def num_active_smells
|
|
92
|
+
@smells.length
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Returns the number of masked smells in the source.
|
|
97
|
+
#
|
|
98
|
+
# @deprecated Masked smells are no longer reported; this method always returns 0.
|
|
99
|
+
#
|
|
100
|
+
def num_masked_smells
|
|
101
|
+
0
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rake'
|
|
4
|
+
require 'rake/tasklib'
|
|
5
|
+
|
|
6
|
+
module Reek
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# Defines a task library for running reek.
|
|
10
|
+
# (Classes here will be configured via the Rakefile, and therefore will
|
|
11
|
+
# possess a :reek:attribute or two.)
|
|
12
|
+
#
|
|
13
|
+
module Rake
|
|
14
|
+
|
|
15
|
+
# A Rake task that runs reek on a set of source files.
|
|
16
|
+
#
|
|
17
|
+
# Example:
|
|
18
|
+
#
|
|
19
|
+
# require 'reek/rake/task'
|
|
20
|
+
#
|
|
21
|
+
# Reek::Rake::Task.new do |t|
|
|
22
|
+
# t.fail_on_error = false
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# This will create a task that can be run with:
|
|
26
|
+
#
|
|
27
|
+
# rake reek
|
|
28
|
+
#
|
|
29
|
+
# Examples:
|
|
30
|
+
#
|
|
31
|
+
# rake reek # checks lib/**/*.rb
|
|
32
|
+
# rake reek REEK_SRC=just_one_file.rb # checks a single source file
|
|
33
|
+
# rake reek REEK_OPTS=-s # sorts the report by smell
|
|
34
|
+
#
|
|
35
|
+
class Task < ::Rake::TaskLib
|
|
36
|
+
|
|
37
|
+
# Name of reek task.
|
|
38
|
+
# Defaults to :reek.
|
|
39
|
+
attr_accessor :name
|
|
40
|
+
|
|
41
|
+
# Array of directories to be added to $LOAD_PATH before running reek.
|
|
42
|
+
# Defaults to ['<the absolute path to reek's lib directory>']
|
|
43
|
+
attr_accessor :libs
|
|
44
|
+
|
|
45
|
+
# Glob pattern to match config files.
|
|
46
|
+
# Setting the REEK_CFG environment variable overrides this.
|
|
47
|
+
# Defaults to 'config/**/*.reek'.
|
|
48
|
+
attr_accessor :config_files
|
|
49
|
+
|
|
50
|
+
# Glob pattern to match source files.
|
|
51
|
+
# Setting the REEK_SRC environment variable overrides this.
|
|
52
|
+
# Defaults to 'lib/**/*.rb'.
|
|
53
|
+
attr_accessor :source_files
|
|
54
|
+
|
|
55
|
+
# String containing commandline options to be passed to Reek.
|
|
56
|
+
# Setting the REEK_OPTS environment variable overrides this value.
|
|
57
|
+
# Defaults to ''.
|
|
58
|
+
attr_accessor :reek_opts
|
|
59
|
+
|
|
60
|
+
# Array of commandline options to pass to ruby. Defaults to [].
|
|
61
|
+
attr_accessor :ruby_opts
|
|
62
|
+
|
|
63
|
+
# Whether or not to fail Rake when an error occurs (typically when smells are found).
|
|
64
|
+
# Defaults to true.
|
|
65
|
+
attr_accessor :fail_on_error
|
|
66
|
+
|
|
67
|
+
# Use verbose output. If this is set to true, the task will print
|
|
68
|
+
# the reek command to stdout. Defaults to false.
|
|
69
|
+
attr_accessor :verbose
|
|
70
|
+
|
|
71
|
+
# Defines a new task, using the name +name+.
|
|
72
|
+
def initialize(name = :reek)
|
|
73
|
+
@name = name
|
|
74
|
+
@libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
|
|
75
|
+
@config_files = nil
|
|
76
|
+
@source_files = nil
|
|
77
|
+
@ruby_opts = []
|
|
78
|
+
@reek_opts = ''
|
|
79
|
+
@fail_on_error = true
|
|
80
|
+
@sort = nil
|
|
81
|
+
|
|
82
|
+
yield self if block_given?
|
|
83
|
+
@config_files ||= 'config/**/*.reek'
|
|
84
|
+
@source_files ||= 'lib/**/*.rb'
|
|
85
|
+
define
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def define # :nodoc:
|
|
91
|
+
desc 'Check for code smells' unless ::Rake.application.last_comment
|
|
92
|
+
task(name) { run_task }
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def run_task
|
|
97
|
+
return if source_file_list.empty?
|
|
98
|
+
cmd = cmd_words.join(' ')
|
|
99
|
+
puts cmd if @verbose
|
|
100
|
+
raise('Smells found!') if !system(cmd) and fail_on_error
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.reek_script
|
|
104
|
+
File.expand_path(File.dirname(__FILE__) + '/../../../bin/reek')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.ruby_exe
|
|
108
|
+
File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def cmd_words
|
|
112
|
+
[Task.ruby_exe] +
|
|
113
|
+
ruby_options +
|
|
114
|
+
[ %Q|"#{Task.reek_script}"| ] +
|
|
115
|
+
[sort_option] +
|
|
116
|
+
config_file_list.collect { |fn| ['-c', %["#{fn}"]] }.flatten +
|
|
117
|
+
source_file_list.collect { |fn| %["#{fn}"] }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def config_file_list
|
|
121
|
+
files = ENV['REEK_CFG'] || @config_files
|
|
122
|
+
return [] unless files
|
|
123
|
+
return FileList[files]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def ruby_options
|
|
127
|
+
lib_path = @libs.join(File::PATH_SEPARATOR)
|
|
128
|
+
@ruby_opts.clone << "-I\"#{lib_path}\""
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def sort_option
|
|
132
|
+
ENV['REEK_OPTS'] || @reek_opts
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def source_file_list # :nodoc:
|
|
136
|
+
files = ENV['REEK_SRC'] || @source_files
|
|
137
|
+
return [] unless files
|
|
138
|
+
return FileList[files]
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|