reek 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -21
  3. data/.travis.yml +1 -0
  4. data/.yardopts +3 -6
  5. data/CHANGELOG +6 -0
  6. data/CONTRIBUTING.md +8 -3
  7. data/README.md +94 -42
  8. data/config/defaults.reek +0 -1
  9. data/docs/API.md +50 -0
  10. data/docs/Attribute.md +43 -0
  11. data/docs/Basic-Smell-Options.md +44 -0
  12. data/docs/Boolean-Parameter.md +52 -0
  13. data/docs/Class-Variable.md +40 -0
  14. data/docs/Code-Smells.md +34 -0
  15. data/docs/Command-Line-Options.md +84 -0
  16. data/docs/Configuration-Files.md +38 -0
  17. data/docs/Control-Couple.md +22 -0
  18. data/docs/Control-Parameter.md +29 -0
  19. data/docs/Data-Clump.md +44 -0
  20. data/docs/Duplicate-Method-Call.md +49 -0
  21. data/docs/Feature-Envy.md +29 -0
  22. data/docs/How-reek-works-internally.md +44 -0
  23. data/docs/Irresponsible-Module.md +39 -0
  24. data/docs/Large-Class.md +20 -0
  25. data/docs/Long-Parameter-List.md +38 -0
  26. data/docs/Long-Yield-List.md +36 -0
  27. data/docs/Module-Initialize.md +62 -0
  28. data/docs/Nested-Iterators.md +38 -0
  29. data/docs/Nil-Check.md +39 -0
  30. data/docs/Prima-Donna-Method.md +53 -0
  31. data/docs/RSpec-matchers.md +133 -0
  32. data/docs/Rake-Task.md +58 -0
  33. data/docs/Reek-Driven-Development.md +45 -0
  34. data/docs/Repeated-Conditional.md +44 -0
  35. data/docs/Simulated-Polymorphism.md +16 -0
  36. data/docs/Smell-Suppression.md +32 -0
  37. data/docs/Too-Many-Instance-Variables.md +43 -0
  38. data/docs/Too-Many-Methods.md +55 -0
  39. data/docs/Too-Many-Statements.md +50 -0
  40. data/docs/Uncommunicative-Method-Name.md +24 -0
  41. data/docs/Uncommunicative-Module-Name.md +23 -0
  42. data/docs/Uncommunicative-Name.md +16 -0
  43. data/docs/Uncommunicative-Parameter-Name.md +24 -0
  44. data/docs/Uncommunicative-Variable-Name.md +24 -0
  45. data/docs/Unused-Parameters.md +27 -0
  46. data/docs/Utility-Function.md +46 -0
  47. data/docs/Versioning-Policy.md +7 -0
  48. data/docs/YAML-Reports.md +111 -0
  49. data/docs/yard_plugin.rb +14 -0
  50. data/features/command_line_interface/options.feature +1 -0
  51. data/features/programmatic_access.feature +1 -1
  52. data/features/samples.feature +3 -3
  53. data/lib/reek.rb +2 -2
  54. data/lib/reek/cli/input.rb +2 -2
  55. data/lib/reek/cli/option_interpreter.rb +2 -0
  56. data/lib/reek/cli/options.rb +10 -4
  57. data/lib/reek/cli/reek_command.rb +2 -2
  58. data/lib/reek/cli/report/report.rb +60 -0
  59. data/lib/reek/cli/silencer.rb +13 -0
  60. data/lib/reek/{source → core}/ast_node.rb +1 -1
  61. data/lib/reek/{source → core}/ast_node_class_map.rb +10 -11
  62. data/lib/reek/{source → core}/code_comment.rb +1 -1
  63. data/lib/reek/core/code_context.rb +1 -1
  64. data/lib/reek/core/examiner.rb +85 -0
  65. data/lib/reek/core/method_context.rb +1 -1
  66. data/lib/reek/core/module_context.rb +2 -2
  67. data/lib/reek/core/reference_collector.rb +31 -0
  68. data/lib/reek/core/singleton_method_context.rb +0 -4
  69. data/lib/reek/core/smell_repository.rb +4 -2
  70. data/lib/reek/{source → core}/tree_dresser.rb +1 -1
  71. data/lib/reek/{source → sexp}/sexp_extensions.rb +5 -5
  72. data/lib/reek/sexp/sexp_formatter.rb +29 -0
  73. data/lib/reek/sexp/sexp_node.rb +91 -0
  74. data/lib/reek/smells.rb +4 -2
  75. data/lib/reek/smells/attribute.rb +35 -7
  76. data/lib/reek/smells/boolean_parameter.rb +1 -1
  77. data/lib/reek/smells/class_variable.rb +1 -1
  78. data/lib/reek/smells/control_parameter.rb +1 -1
  79. data/lib/reek/smells/data_clump.rb +1 -1
  80. data/lib/reek/smells/duplicate_method_call.rb +12 -4
  81. data/lib/reek/smells/feature_envy.rb +1 -1
  82. data/lib/reek/smells/irresponsible_module.rb +3 -3
  83. data/lib/reek/smells/long_parameter_list.rb +1 -1
  84. data/lib/reek/smells/long_yield_list.rb +1 -1
  85. data/lib/reek/smells/module_initialize.rb +1 -1
  86. data/lib/reek/smells/nested_iterators.rb +1 -1
  87. data/lib/reek/smells/nil_check.rb +3 -2
  88. data/lib/reek/smells/prima_donna_method.rb +18 -11
  89. data/lib/reek/smells/repeated_conditional.rb +3 -3
  90. data/lib/reek/smells/smell_detector.rb +5 -1
  91. data/lib/reek/smells/smell_warning.rb +99 -0
  92. data/lib/reek/smells/too_many_instance_variables.rb +1 -1
  93. data/lib/reek/smells/too_many_methods.rb +1 -1
  94. data/lib/reek/smells/too_many_statements.rb +1 -1
  95. data/lib/reek/smells/uncommunicative_method_name.rb +1 -1
  96. data/lib/reek/smells/uncommunicative_module_name.rb +1 -1
  97. data/lib/reek/smells/uncommunicative_parameter_name.rb +1 -1
  98. data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
  99. data/lib/reek/smells/unused_parameters.rb +1 -1
  100. data/lib/reek/smells/utility_function.rb +3 -16
  101. data/lib/reek/source/source_code.rb +31 -13
  102. data/lib/reek/source/source_locator.rb +16 -17
  103. data/lib/reek/source/source_repository.rb +10 -11
  104. data/lib/reek/spec/should_reek.rb +2 -2
  105. data/lib/reek/spec/should_reek_of.rb +2 -2
  106. data/lib/reek/spec/should_reek_only_of.rb +2 -2
  107. data/lib/reek/version.rb +1 -1
  108. data/reek.gemspec +3 -4
  109. data/spec/factories/factories.rb +1 -1
  110. data/spec/gem/yard_spec.rb +1 -1
  111. data/spec/quality/reek_source_spec.rb +2 -2
  112. data/spec/reek/cli/html_report_spec.rb +3 -3
  113. data/spec/reek/cli/json_report_spec.rb +3 -3
  114. data/spec/reek/cli/{option_interperter_spec.rb → option_interpreter_spec.rb} +1 -1
  115. data/spec/reek/cli/options_spec.rb +19 -0
  116. data/spec/reek/cli/text_report_spec.rb +7 -7
  117. data/spec/reek/cli/xml_report_spec.rb +34 -0
  118. data/spec/reek/cli/yaml_report_spec.rb +3 -3
  119. data/spec/reek/configuration/app_configuration_spec.rb +1 -1
  120. data/spec/reek/configuration/configuration_file_finder_spec.rb +22 -1
  121. data/spec/reek/{source → core}/code_comment_spec.rb +14 -14
  122. data/spec/reek/core/code_context_spec.rb +1 -1
  123. data/spec/reek/{examiner_spec.rb → core/examiner_spec.rb} +12 -12
  124. data/spec/reek/core/method_context_spec.rb +27 -22
  125. data/spec/reek/core/module_context_spec.rb +2 -2
  126. data/spec/reek/core/object_refs_spec.rb +1 -1
  127. data/spec/reek/{source → core}/object_source_spec.rb +1 -1
  128. data/spec/reek/{source → core}/reference_collector_spec.rb +25 -16
  129. data/spec/reek/core/singleton_method_context_spec.rb +12 -2
  130. data/spec/reek/core/smell_configuration_spec.rb +1 -1
  131. data/spec/reek/core/smell_repository_spec.rb +12 -1
  132. data/spec/reek/core/stop_context_spec.rb +1 -1
  133. data/spec/reek/core/tree_dresser_spec.rb +16 -0
  134. data/spec/reek/core/tree_walker_spec.rb +3 -3
  135. data/spec/reek/core/warning_collector_spec.rb +6 -6
  136. data/spec/reek/{source → sexp}/sexp_extensions_spec.rb +8 -8
  137. data/spec/reek/{source → sexp}/sexp_formatter_spec.rb +11 -5
  138. data/spec/reek/{source → sexp}/sexp_node_spec.rb +3 -3
  139. data/spec/reek/smells/attribute_spec.rb +89 -85
  140. data/spec/reek/smells/behaves_like_variable_detector.rb +1 -1
  141. data/spec/reek/smells/boolean_parameter_spec.rb +1 -1
  142. data/spec/reek/smells/class_variable_spec.rb +1 -1
  143. data/spec/reek/smells/control_parameter_spec.rb +1 -1
  144. data/spec/reek/smells/data_clump_spec.rb +2 -2
  145. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
  146. data/spec/reek/smells/feature_envy_spec.rb +2 -2
  147. data/spec/reek/smells/irresponsible_module_spec.rb +1 -1
  148. data/spec/reek/smells/long_parameter_list_spec.rb +2 -2
  149. data/spec/reek/smells/long_yield_list_spec.rb +1 -1
  150. data/spec/reek/smells/module_initialize_spec.rb +1 -1
  151. data/spec/reek/smells/nested_iterators_spec.rb +2 -2
  152. data/spec/reek/smells/nil_check_spec.rb +1 -1
  153. data/spec/reek/smells/prima_donna_method_spec.rb +1 -1
  154. data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
  155. data/spec/reek/smells/smell_detector_shared.rb +2 -2
  156. data/spec/reek/{smell_warning_spec.rb → smells/smell_warning_spec.rb} +7 -7
  157. data/spec/reek/smells/too_many_instance_variables_spec.rb +1 -1
  158. data/spec/reek/smells/too_many_methods_spec.rb +1 -1
  159. data/spec/reek/smells/too_many_statements_spec.rb +4 -4
  160. data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -1
  161. data/spec/reek/smells/uncommunicative_module_name_spec.rb +1 -1
  162. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +1 -1
  163. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +1 -1
  164. data/spec/reek/smells/unused_parameters_spec.rb +1 -1
  165. data/spec/reek/smells/utility_function_spec.rb +1 -1
  166. data/spec/reek/source/source_code_spec.rb +1 -1
  167. data/spec/reek/spec/should_reek_of_spec.rb +1 -1
  168. data/spec/reek/spec/should_reek_only_of_spec.rb +1 -1
  169. data/spec/reek/spec/should_reek_spec.rb +1 -1
  170. data/spec/samples/checkstyle.xml +2 -0
  171. data/spec/spec_helper.rb +15 -3
  172. metadata +68 -38
  173. data/.ruby-gemset +0 -1
  174. data/lib/reek/examiner.rb +0 -79
  175. data/lib/reek/smell_warning.rb +0 -87
  176. data/lib/reek/source/reference_collector.rb +0 -27
  177. data/lib/reek/source/sexp_formatter.rb +0 -22
  178. data/lib/reek/source/sexp_node.rb +0 -79
  179. data/spec/reek/source/tree_dresser_spec.rb +0 -16
@@ -1,5 +1,5 @@
1
1
  require_relative 'command'
2
- require_relative '../examiner'
2
+ require_relative '../core/examiner'
3
3
 
4
4
  module Reek
5
5
  module CLI
@@ -10,7 +10,7 @@ module Reek
10
10
  class ReekCommand < Command
11
11
  def execute(app)
12
12
  @options.sources.each do |source|
13
- reporter.add_examiner Examiner.new(source, smell_names)
13
+ reporter.add_examiner Core::Examiner.new(source, smell_names)
14
14
  end
15
15
  reporter.smells? ? app.report_smells : app.report_success
16
16
  reporter.show
@@ -126,6 +126,66 @@ module Reek
126
126
  print("Html file saved\n")
127
127
  end
128
128
  end
129
+
130
+ #
131
+ # Generates a list of smells in XML format
132
+ #
133
+ class XMLReport < Base
134
+ require 'rexml/document'
135
+
136
+ def initialize(options = {})
137
+ super options
138
+ end
139
+
140
+ def show
141
+ checkstyle = REXML::Element.new('checkstyle', document)
142
+
143
+ smells.group_by(&:source).each do |file, file_smells|
144
+ file_to_xml(file, file_smells, checkstyle)
145
+ end
146
+
147
+ print_xml(checkstyle.parent)
148
+ end
149
+
150
+ private
151
+
152
+ def document
153
+ REXML::Document.new.tap do |doc|
154
+ doc << REXML::XMLDecl.new
155
+ end
156
+ end
157
+
158
+ def file_to_xml(file, file_smells, parent)
159
+ REXML::Element.new('file', parent).tap do |element|
160
+ element.attributes['name'] = File.realpath(file)
161
+ smells_to_xml(file_smells, element)
162
+ end
163
+ end
164
+
165
+ def smells_to_xml(smells, parent)
166
+ smells.each do |smell|
167
+ smell_to_xml(smell, parent)
168
+ end
169
+ end
170
+
171
+ def smell_to_xml(smell, parent)
172
+ REXML::Element.new('error', parent).tap do |element|
173
+ attributes = [
174
+ ['line', smell.lines.first],
175
+ ['column', 0],
176
+ ['severity', 'warning'],
177
+ ['message', smell.message],
178
+ ['source', smell.smell_type]
179
+ ]
180
+ element.add_attributes(attributes)
181
+ end
182
+ end
183
+
184
+ def print_xml(document)
185
+ formatter = REXML::Formatters::Default.new
186
+ puts formatter.write(document, '')
187
+ end
188
+ end
129
189
  end
130
190
  end
131
191
  end
@@ -0,0 +1,13 @@
1
+ module Reek
2
+ module CLI
3
+ # CLI silencer
4
+ module Silencer
5
+ def self.silently
6
+ old_verbose, $VERBOSE = $VERBOSE, nil
7
+ yield if block_given?
8
+ ensure
9
+ $VERBOSE = old_verbose
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,7 @@
1
1
  require 'parser'
2
2
 
3
3
  module Reek
4
- module Source
4
+ module Core
5
5
  # Base class for AST nodes extended with utility methods. Contains some
6
6
  # methods to ease the transition from Sexp to AST::Node.
7
7
  class ASTNode < Parser::AST::Node
@@ -1,9 +1,9 @@
1
1
  require_relative 'ast_node'
2
- require_relative 'sexp_node'
3
- require_relative 'sexp_extensions'
2
+ require_relative '../sexp/sexp_node'
3
+ require_relative '../sexp/sexp_extensions'
4
4
 
5
5
  module Reek
6
- module Source
6
+ module Core
7
7
  # Maps AST node types to sublasses of ASTNode extended with the relevant
8
8
  # utility modules.
9
9
  class ASTNodeClassMap
@@ -12,21 +12,20 @@ module Reek
12
12
  end
13
13
 
14
14
  def klass_for(type)
15
- @klass_map[type] ||=
16
- begin
17
- klass = Class.new(ASTNode)
18
- klass.send :include, extension_map[type] if extension_map[type]
19
- klass.send :include, SexpNode
20
- end
15
+ @klass_map[type] ||= Class.new(ASTNode).tap do |klass|
16
+ extension = extension_map[type]
17
+ klass.send :include, extension if extension
18
+ klass.send :include, Sexp::SexpNode
19
+ end
21
20
  end
22
21
 
23
22
  def extension_map
24
23
  @extension_map ||=
25
24
  begin
26
- assoc = SexpExtensions.constants.map do |const|
25
+ assoc = Sexp::SexpExtensions.constants.map do |const|
27
26
  [
28
27
  const.to_s.sub(/Node$/, '').downcase.to_sym,
29
- SexpExtensions.const_get(const)
28
+ Sexp::SexpExtensions.const_get(const)
30
29
  ]
31
30
  end
32
31
  Hash[assoc]
@@ -1,7 +1,7 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Reek
4
- module Source
4
+ module Core
5
5
  #
6
6
  # A comment header from an abstract syntax tree; found directly above
7
7
  # module, class and method definitions.
@@ -97,7 +97,7 @@ module Reek
97
97
 
98
98
  def config
99
99
  @config ||= if @exp
100
- Source::CodeComment.new(@exp.comments || '').config
100
+ Core::CodeComment.new(@exp.comments || '').config
101
101
  else
102
102
  {}
103
103
  end
@@ -0,0 +1,85 @@
1
+ # NOTE: tree_walker is required first to ensure unparser is required before
2
+ # parser. This prevents a potentially incompatible version of parser from being
3
+ # loaded first. This is only relevant when running bin/reek straight from a
4
+ # checkout directory without using Bundler.
5
+ #
6
+ # See also https://github.com/troessner/reek/pull/468
7
+ require_relative 'tree_walker'
8
+ require_relative 'smell_repository'
9
+ require_relative 'warning_collector'
10
+ require_relative '../source/source_repository'
11
+
12
+ module Reek
13
+ module Core
14
+ #
15
+ # Applies all available smell detectors to a source.
16
+ #
17
+ class Examiner
18
+ #
19
+ # A simple description of the source being analysed for smells.
20
+ # If the source is a single File, this will be the file's path.
21
+ #
22
+ attr_accessor :description
23
+
24
+ #
25
+ # Creates an Examiner which scans the given +source+ for code smells.
26
+ #
27
+ # @param source [Source::SourceCode, Array<String>, #to_reek_source]
28
+ # If +source+ is a String it is assumed to be Ruby source code;
29
+ # if it is a File, the file is opened and parsed for Ruby source code;
30
+ # and if it is an Array, it is assumed to be a list of file paths,
31
+ # each of which is opened and parsed for source code.
32
+ #
33
+ def initialize(source, smell_types_to_filter_by = [])
34
+ @sources = Source::SourceRepository.parse(source)
35
+ @description = @sources.description
36
+ @collector = Core::WarningCollector.new
37
+ @smell_types = eligible_smell_types(smell_types_to_filter_by)
38
+
39
+ run
40
+ end
41
+
42
+ #
43
+ # @return [Array<SmellWarning>] the smells found in the source
44
+ #
45
+ def smells
46
+ @smells ||= @collector.warnings
47
+ end
48
+
49
+ #
50
+ # @return [Integer] the number of smells found in the source
51
+ #
52
+ def smells_count
53
+ smells.length
54
+ end
55
+
56
+ #
57
+ # @return [Boolean] true if and only if there are code smells in the source.
58
+ #
59
+ def smelly?
60
+ !smells.empty?
61
+ end
62
+
63
+ private
64
+
65
+ def run
66
+ @sources.each do |source|
67
+ smell_repository = Core::SmellRepository.new(source.description, @smell_types)
68
+ syntax_tree = source.syntax_tree
69
+ Core::TreeWalker.new(smell_repository).process(syntax_tree) if syntax_tree
70
+ smell_repository.report_on(@collector)
71
+ end
72
+ end
73
+
74
+ def eligible_smell_types(smell_types_to_filter_by = [])
75
+ if smell_types_to_filter_by.any?
76
+ Core::SmellRepository.smell_types.select do |klass|
77
+ smell_types_to_filter_by.include? klass.smell_type
78
+ end
79
+ else
80
+ Core::SmellRepository.smell_types
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -25,7 +25,7 @@ module Reek
25
25
  attr_reader :num_statements
26
26
 
27
27
  def initialize(outer, exp)
28
- super(outer, exp)
28
+ super
29
29
  @parameters = exp.parameters.dup
30
30
  @parameters.extend MethodParameters
31
31
  @num_statements = 0
@@ -1,5 +1,5 @@
1
1
  require_relative 'code_context'
2
- require_relative '../source/sexp_formatter'
2
+ require_relative '../sexp/sexp_formatter'
3
3
 
4
4
  module Reek
5
5
  module Core
@@ -9,7 +9,7 @@ module Reek
9
9
  class ModuleContext < CodeContext
10
10
  def initialize(outer, exp)
11
11
  super(outer, exp)
12
- @name = Source::SexpFormatter.format(exp.children.first)
12
+ @name = Sexp::SexpFormatter.format(exp.children.first)
13
13
  end
14
14
 
15
15
  def node_instance_methods
@@ -0,0 +1,31 @@
1
+ module Reek
2
+ module Core
3
+ #
4
+ # Locates references to the current object within a portion
5
+ # of an abstract syntax tree.
6
+ #
7
+ class ReferenceCollector
8
+ STOP_NODES = [:class, :module, :def, :defs]
9
+
10
+ def initialize(ast)
11
+ @ast = ast
12
+ end
13
+
14
+ def num_refs_to_self
15
+ (explicit_self_calls + implicit_self_calls).size
16
+ end
17
+
18
+ private
19
+
20
+ def explicit_self_calls
21
+ [:self, :zsuper, :ivar, :ivasgn].flat_map do |node_type|
22
+ @ast.each_node(node_type, STOP_NODES)
23
+ end
24
+ end
25
+
26
+ def implicit_self_calls
27
+ @ast.each_node(:send, STOP_NODES).reject(&:receiver)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,10 +6,6 @@ module Reek
6
6
  # A context wrapper for any singleton method definition found in a syntax tree.
7
7
  #
8
8
  class SingletonMethodContext < MethodContext
9
- def initialize(outer, exp)
10
- super(outer, exp)
11
- end
12
-
13
9
  def envious_receivers
14
10
  []
15
11
  end
@@ -11,7 +11,7 @@ module Reek
11
11
  attr_reader :detectors
12
12
 
13
13
  def self.smell_types
14
- Reek::Smells::SmellDetector.descendants
14
+ Reek::Smells::SmellDetector.descendants.sort_by(&:name)
15
15
  end
16
16
 
17
17
  def initialize(source_description = nil, smell_types = self.class.smell_types)
@@ -24,7 +24,9 @@ module Reek
24
24
  end
25
25
 
26
26
  def configure(klass, config)
27
- @detectors[klass].configure_with(config) if @detectors[klass]
27
+ detector = @detectors[klass]
28
+ raise ArgumentError, "Unknown smell type #{klass} found in configuration" unless detector
29
+ detector.configure_with(config)
28
30
  end
29
31
 
30
32
  def report_on(listener)
@@ -1,7 +1,7 @@
1
1
  require_relative 'ast_node_class_map'
2
2
 
3
3
  module Reek
4
- module Source
4
+ module Core
5
5
  #
6
6
  # Adorns an abstract syntax tree with mix-in modules to make accessing
7
7
  # the tree more understandable and less implementation-dependent.
@@ -1,8 +1,8 @@
1
1
  require_relative 'sexp_node'
2
- require_relative 'reference_collector'
2
+ require_relative '../core/reference_collector'
3
3
 
4
4
  module Reek
5
- module Source
5
+ module Sexp
6
6
  #
7
7
  # Extension modules providing utility methods to ASTNode objects, depending
8
8
  # on their type.
@@ -242,7 +242,7 @@ module Reek
242
242
  end
243
243
 
244
244
  def depends_on_instance?
245
- ReferenceCollector.new(self).num_refs_to_self > 0
245
+ Core::ReferenceCollector.new(self).num_refs_to_self > 0
246
246
  end
247
247
  end
248
248
 
@@ -259,7 +259,7 @@ module Reek
259
259
  include MethodNodeBase
260
260
  def full_name(outer)
261
261
  prefix = outer == '' ? '' : "#{outer}#"
262
- "#{prefix}#{SexpNode.format(receiver)}.#{name}"
262
+ "#{prefix}#{SexpFormatter.format(receiver)}.#{name}"
263
263
  end
264
264
 
265
265
  def depends_on_instance?
@@ -314,7 +314,7 @@ module Reek
314
314
  end
315
315
 
316
316
  def text_name
317
- SexpNode.format(name)
317
+ SexpFormatter.format(name)
318
318
  end
319
319
  end
320
320
 
@@ -0,0 +1,29 @@
1
+ require_relative '../cli/silencer'
2
+ Reek::CLI::Silencer.silently do
3
+ require 'unparser'
4
+ end
5
+
6
+ module Reek
7
+ module Sexp
8
+ #
9
+ # Formats snippets of syntax tree back into Ruby source code.
10
+ #
11
+ # :reek:DuplicateMethodCall { max_calls: 2 } is ok for lines.first
12
+ class SexpFormatter
13
+ # Formats the given sexp.
14
+ #
15
+ # sexp - S-expression of type AST::Node or something that is at least to_s-able.
16
+ #
17
+ # Returns a formatted string representation.
18
+ def self.format(sexp)
19
+ return sexp.to_s unless sexp.is_a? AST::Node
20
+ lines = Unparser.unparse(sexp).split "\n"
21
+ case lines.length
22
+ when 1 then lines.first
23
+ when 2 then lines.join('; ')
24
+ else [lines.first, lines.last].join(' ... ')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,91 @@
1
+ module Reek
2
+ module Sexp
3
+ #
4
+ # Extensions to +Sexp+ to allow +TreeWalker+ to navigate the abstract
5
+ # syntax tree more easily.
6
+ #
7
+ module SexpNode
8
+ #
9
+ # Carries out a depth-first traversal of this syntax tree, yielding
10
+ # every Sexp of type `target_type`. The traversal ignores any node
11
+ # whose type is listed in the Array `ignoring`.
12
+ # Takes a block as well.
13
+ #
14
+ # target_type - the type to look for, e.g. :send, :block
15
+ # ignoring - types to ignore, e.g. [:casgn, :class, :module]
16
+ # blk - block to execute for every hit
17
+ #
18
+ # Examples:
19
+ # context.each_node(:send, [:mlhs]) do |call_node| .... end
20
+ # context.each_node(:lvar).any? { |it| it.var_name == 'something' }
21
+ #
22
+ # Returns an array with all matching nodes.
23
+ def each_node(target_type, ignoring = [], &blk)
24
+ if block_given?
25
+ look_for_type(target_type, ignoring, &blk)
26
+ else
27
+ result = []
28
+ look_for_type(target_type, ignoring) { |exp| result << exp }
29
+ result
30
+ end
31
+ end
32
+
33
+ #
34
+ # Carries out a depth-first traversal of this syntax tree, yielding
35
+ # every Sexp of type `target_type`. The traversal ignores any node
36
+ # whose type is listed in the Array `ignoring`, including the top node.
37
+ # Takes a block as well.
38
+ #
39
+ # target_types - the types to look for, e.g. [:send, :block]
40
+ # ignoring - types to ignore, e.g. [:casgn, :class, :module]
41
+ # blk - block to execute for every hit
42
+ #
43
+ # Examples:
44
+ # exp.find_nodes([:block]).flat_map do |elem| ... end
45
+ #
46
+ # Returns an array with all matching nodes.
47
+ def find_nodes(target_types, ignoring = [])
48
+ result = []
49
+ look_for_types(target_types, ignoring) { |exp| result << exp }
50
+ result
51
+ end
52
+
53
+ def contains_nested_node?(target_type)
54
+ look_for_type(target_type) { |_elem| return true }
55
+ false
56
+ end
57
+
58
+ def format_to_ruby
59
+ SexpFormatter.format(self)
60
+ end
61
+
62
+ protected
63
+
64
+ # See ".each_node" for documentation.
65
+ def look_for_type(target_type, ignoring = [], &blk)
66
+ each_sexp do |elem|
67
+ elem.look_for_type(target_type, ignoring, &blk) unless ignoring.include?(elem.type)
68
+ end
69
+ blk.call(self) if type == target_type
70
+ end
71
+
72
+ # See ".find_nodes" for documentation.
73
+ def look_for_types(target_types, ignoring = [], &blk)
74
+ return if ignoring.include?(type)
75
+ if target_types.include? type
76
+ blk.call(self)
77
+ else
78
+ each_sexp do |elem|
79
+ elem.look_for_types(target_types, ignoring, &blk)
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def each_sexp
87
+ children.each { |elem| yield elem if elem.is_a? AST::Node }
88
+ end
89
+ end
90
+ end
91
+ end