reek 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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