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.
Files changed (209) hide show
  1. data/.yardopts +10 -0
  2. data/History.txt +62 -0
  3. data/README.md +74 -0
  4. data/bin/reek +2 -2
  5. data/config/defaults.reek +44 -5
  6. data/features/api.feature +20 -0
  7. data/features/masking_smells.feature +60 -42
  8. data/features/options.feature +7 -2
  9. data/features/rake_task.feature +29 -22
  10. data/features/reports.feature +9 -41
  11. data/features/samples.feature +184 -196
  12. data/features/stdin.feature +5 -8
  13. data/features/step_definitions/reek_steps.rb +8 -4
  14. data/features/support/env.rb +2 -3
  15. data/features/yaml.feature +85 -0
  16. data/lib/reek/cli/application.rb +46 -0
  17. data/lib/reek/cli/command_line.rb +108 -0
  18. data/lib/reek/cli/help_command.rb +18 -0
  19. data/lib/reek/cli/reek_command.rb +37 -0
  20. data/lib/reek/cli/report.rb +58 -0
  21. data/lib/reek/cli/version_command.rb +19 -0
  22. data/lib/reek/cli/yaml_command.rb +32 -0
  23. data/lib/reek/core/code_context.rb +64 -0
  24. data/lib/reek/core/code_parser.rb +166 -0
  25. data/lib/reek/core/method_context.rb +84 -0
  26. data/lib/reek/core/module_context.rb +20 -0
  27. data/lib/reek/core/object_refs.rb +51 -0
  28. data/lib/reek/core/singleton_method_context.rb +20 -0
  29. data/lib/reek/core/smell_configuration.rb +62 -0
  30. data/lib/reek/core/sniffer.rb +105 -0
  31. data/lib/reek/core/stop_context.rb +30 -0
  32. data/lib/reek/core/warning_collector.rb +27 -0
  33. data/lib/reek/examiner.rb +104 -0
  34. data/lib/reek/rake/task.rb +142 -0
  35. data/lib/reek/smell_warning.rb +73 -23
  36. data/lib/reek/smells/attribute.rb +26 -21
  37. data/lib/reek/smells/boolean_parameter.rb +38 -0
  38. data/lib/reek/smells/class_variable.rb +22 -9
  39. data/lib/reek/smells/control_couple.rb +40 -16
  40. data/lib/reek/smells/data_clump.rb +114 -23
  41. data/lib/reek/smells/duplication.rb +55 -15
  42. data/lib/reek/smells/feature_envy.rb +16 -7
  43. data/lib/reek/smells/irresponsible_module.rb +37 -0
  44. data/lib/reek/smells/large_class.rb +37 -19
  45. data/lib/reek/smells/long_method.rb +20 -9
  46. data/lib/reek/smells/long_parameter_list.rb +22 -10
  47. data/lib/reek/smells/long_yield_list.rb +43 -7
  48. data/lib/reek/smells/nested_iterators.rb +68 -8
  49. data/lib/reek/smells/simulated_polymorphism.rb +26 -15
  50. data/lib/reek/smells/smell_detector.rb +29 -51
  51. data/lib/reek/smells/uncommunicative_method_name.rb +74 -0
  52. data/lib/reek/smells/uncommunicative_module_name.rb +74 -0
  53. data/lib/reek/smells/uncommunicative_parameter_name.rb +79 -0
  54. data/lib/reek/smells/uncommunicative_variable_name.rb +89 -0
  55. data/lib/reek/smells/utility_function.rb +51 -12
  56. data/lib/reek/smells.rb +29 -0
  57. data/lib/reek/source/code_comment.rb +37 -0
  58. data/lib/reek/source/config_file.rb +72 -0
  59. data/lib/reek/source/core_extras.rb +46 -0
  60. data/lib/reek/source/reference_collector.rb +28 -0
  61. data/lib/reek/source/sexp_formatter.rb +17 -0
  62. data/lib/reek/source/source_code.rb +44 -0
  63. data/lib/reek/source/source_file.rb +32 -0
  64. data/lib/reek/source/source_locator.rb +42 -0
  65. data/lib/reek/source/tree_dresser.rb +204 -0
  66. data/lib/reek/source.rb +18 -0
  67. data/lib/reek/spec/should_reek.rb +31 -0
  68. data/lib/reek/spec/should_reek_of.rb +37 -0
  69. data/lib/reek/spec/should_reek_only_of.rb +37 -0
  70. data/lib/reek/spec.rb +51 -0
  71. data/lib/reek.rb +8 -4
  72. data/reek.gemspec +9 -7
  73. data/spec/matchers/smell_of_matcher.rb +58 -0
  74. data/spec/reek/{help_command_spec.rb → cli/help_command_spec.rb} +3 -4
  75. data/spec/reek/cli/reek_command_spec.rb +46 -0
  76. data/spec/reek/cli/report_spec.rb +30 -0
  77. data/spec/reek/{version_command_spec.rb → cli/version_command_spec.rb} +3 -3
  78. data/spec/reek/cli/yaml_command_spec.rb +47 -0
  79. data/spec/reek/core/code_context_spec.rb +145 -0
  80. data/spec/reek/{code_parser_spec.rb → core/code_parser_spec.rb} +7 -6
  81. data/spec/reek/{config_spec.rb → core/config_spec.rb} +2 -6
  82. data/spec/reek/{method_context_spec.rb → core/method_context_spec.rb} +16 -36
  83. data/spec/reek/core/module_context_spec.rb +27 -0
  84. data/spec/reek/{object_refs_spec.rb → core/object_refs_spec.rb} +5 -6
  85. data/spec/reek/core/singleton_method_context_spec.rb +9 -0
  86. data/spec/reek/core/smell_configuration_spec.rb +11 -0
  87. data/spec/reek/core/stop_context_spec.rb +17 -0
  88. data/spec/reek/core/warning_collector_spec.rb +27 -0
  89. data/spec/reek/examiner_spec.rb +103 -0
  90. data/spec/reek/smell_warning_spec.rb +74 -82
  91. data/spec/reek/smells/attribute_spec.rb +42 -23
  92. data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
  93. data/spec/reek/smells/boolean_parameter_spec.rb +66 -0
  94. data/spec/reek/smells/class_variable_spec.rb +60 -75
  95. data/spec/reek/smells/control_couple_spec.rb +37 -32
  96. data/spec/reek/smells/data_clump_spec.rb +63 -24
  97. data/spec/reek/smells/duplication_spec.rb +106 -57
  98. data/spec/reek/smells/feature_envy_spec.rb +113 -92
  99. data/spec/reek/smells/irresponsible_module_spec.rb +58 -0
  100. data/spec/reek/smells/large_class_spec.rb +67 -57
  101. data/spec/reek/smells/long_method_spec.rb +40 -10
  102. data/spec/reek/smells/long_parameter_list_spec.rb +50 -28
  103. data/spec/reek/smells/long_yield_list_spec.rb +57 -0
  104. data/spec/reek/smells/nested_iterators_spec.rb +115 -7
  105. data/spec/reek/smells/simulated_polymorphism_spec.rb +56 -20
  106. data/spec/reek/smells/smell_detector_shared.rb +42 -0
  107. data/spec/reek/smells/uncommunicative_method_name_spec.rb +42 -0
  108. data/spec/reek/smells/uncommunicative_module_name_spec.rb +66 -0
  109. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +71 -0
  110. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +120 -0
  111. data/spec/reek/smells/utility_function_spec.rb +50 -4
  112. data/spec/reek/source/code_comment_spec.rb +82 -0
  113. data/spec/reek/source/object_source_spec.rb +20 -0
  114. data/spec/reek/source/reference_collector_spec.rb +53 -0
  115. data/spec/reek/{adapters/source_spec.rb → source/source_code_spec.rb} +7 -8
  116. data/spec/reek/source/tree_dresser_spec.rb +270 -0
  117. data/spec/reek/spec/should_reek_of_spec.rb +76 -0
  118. data/spec/reek/spec/should_reek_only_of_spec.rb +89 -0
  119. data/spec/reek/{adapters → spec}/should_reek_spec.rb +8 -32
  120. data/spec/samples/all_but_one_masked/clean_one.rb +1 -0
  121. data/spec/samples/all_but_one_masked/dirty.rb +1 -0
  122. data/spec/samples/all_but_one_masked/masked.reek +5 -1
  123. data/spec/samples/clean_due_to_masking/clean_one.rb +1 -0
  124. data/spec/samples/clean_due_to_masking/clean_three.rb +1 -0
  125. data/spec/samples/clean_due_to_masking/clean_two.rb +1 -0
  126. data/spec/samples/clean_due_to_masking/dirty_one.rb +1 -1
  127. data/spec/samples/clean_due_to_masking/dirty_two.rb +1 -1
  128. data/spec/samples/clean_due_to_masking/masked.reek +5 -1
  129. data/spec/samples/config/allow_duplication.reek +3 -0
  130. data/spec/samples/config/deeper_nested_iterators.reek +3 -0
  131. data/spec/samples/corrupt_config_file/dirty.rb +1 -1
  132. data/spec/samples/demo/demo.rb +8 -0
  133. data/spec/samples/empty_config_file/dirty.rb +2 -1
  134. data/spec/samples/exceptions.reek +1 -1
  135. data/spec/samples/inline_config/dirty.rb +16 -0
  136. data/spec/samples/inline_config/masked.reek +7 -0
  137. data/spec/samples/mask_some/dirty.rb +8 -0
  138. data/spec/samples/mask_some/some.reek +8 -0
  139. data/spec/samples/masked/dirty.rb +2 -1
  140. data/spec/samples/masked/masked.reek +3 -1
  141. data/spec/samples/mixed_results/clean_one.rb +1 -0
  142. data/spec/samples/mixed_results/clean_three.rb +1 -0
  143. data/spec/samples/mixed_results/clean_two.rb +1 -0
  144. data/spec/samples/mixed_results/dirty_one.rb +1 -0
  145. data/spec/samples/mixed_results/dirty_two.rb +1 -0
  146. data/spec/samples/not_quite_masked/dirty.rb +2 -1
  147. data/spec/samples/not_quite_masked/masked.reek +1 -1
  148. data/spec/samples/overrides/masked/dirty.rb +2 -1
  149. data/spec/samples/overrides/masked/lower.reek +3 -1
  150. data/spec/samples/three_clean_files/clean_one.rb +1 -0
  151. data/spec/samples/three_clean_files/clean_three.rb +1 -0
  152. data/spec/samples/three_clean_files/clean_two.rb +1 -0
  153. data/spec/samples/two_smelly_files/dirty_one.rb +2 -1
  154. data/spec/samples/two_smelly_files/dirty_two.rb +2 -1
  155. data/spec/spec_helper.rb +10 -2
  156. data/tasks/reek.rake +2 -2
  157. data/tasks/test.rake +12 -3
  158. metadata +121 -79
  159. data/README.rdoc +0 -84
  160. data/features/profile.feature +0 -34
  161. data/lib/reek/adapters/application.rb +0 -46
  162. data/lib/reek/adapters/command_line.rb +0 -77
  163. data/lib/reek/adapters/config_file.rb +0 -31
  164. data/lib/reek/adapters/core_extras.rb +0 -64
  165. data/lib/reek/adapters/rake_task.rb +0 -121
  166. data/lib/reek/adapters/report.rb +0 -86
  167. data/lib/reek/adapters/source.rb +0 -72
  168. data/lib/reek/adapters/spec.rb +0 -133
  169. data/lib/reek/block_context.rb +0 -62
  170. data/lib/reek/class_context.rb +0 -41
  171. data/lib/reek/code_context.rb +0 -68
  172. data/lib/reek/code_parser.rb +0 -203
  173. data/lib/reek/configuration.rb +0 -57
  174. data/lib/reek/detector_stack.rb +0 -37
  175. data/lib/reek/help_command.rb +0 -14
  176. data/lib/reek/if_context.rb +0 -18
  177. data/lib/reek/masking_collection.rb +0 -33
  178. data/lib/reek/method_context.rb +0 -138
  179. data/lib/reek/module_context.rb +0 -49
  180. data/lib/reek/name.rb +0 -57
  181. data/lib/reek/object_refs.rb +0 -49
  182. data/lib/reek/reek_command.rb +0 -28
  183. data/lib/reek/sexp_formatter.rb +0 -10
  184. data/lib/reek/singleton_method_context.rb +0 -26
  185. data/lib/reek/smells/uncommunicative_name.rb +0 -84
  186. data/lib/reek/sniffer.rb +0 -177
  187. data/lib/reek/stop_context.rb +0 -35
  188. data/lib/reek/tree_dresser.rb +0 -82
  189. data/lib/reek/version_command.rb +0 -14
  190. data/lib/reek/yield_call_context.rb +0 -12
  191. data/spec/reek/adapters/report_spec.rb +0 -31
  192. data/spec/reek/adapters/should_reek_of_spec.rb +0 -138
  193. data/spec/reek/adapters/should_reek_only_of_spec.rb +0 -87
  194. data/spec/reek/block_context_spec.rb +0 -65
  195. data/spec/reek/class_context_spec.rb +0 -161
  196. data/spec/reek/code_context_spec.rb +0 -182
  197. data/spec/reek/configuration_spec.rb +0 -12
  198. data/spec/reek/if_context_spec.rb +0 -17
  199. data/spec/reek/masking_collection_spec.rb +0 -236
  200. data/spec/reek/module_context_spec.rb +0 -46
  201. data/spec/reek/name_spec.rb +0 -37
  202. data/spec/reek/object_source_spec.rb +0 -23
  203. data/spec/reek/reek_command_spec.rb +0 -45
  204. data/spec/reek/singleton_method_context_spec.rb +0 -16
  205. data/spec/reek/smells/smell_detector_spec.rb +0 -36
  206. data/spec/reek/smells/uncommunicative_name_spec.rb +0 -146
  207. data/spec/reek/sniffer_spec.rb +0 -11
  208. data/spec/reek/stop_context_spec.rb +0 -33
  209. 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