reek 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -0
  3. data/CHANGELOG.md +19 -12
  4. data/CONTRIBUTING.md +7 -7
  5. data/README.md +91 -28
  6. data/ataru_setup.rb +13 -0
  7. data/{config/defaults.reek → defaults.reek} +0 -0
  8. data/docs/API.md +32 -31
  9. data/docs/Attribute.md +1 -1
  10. data/docs/Basic-Smell-Options.md +2 -1
  11. data/docs/Boolean-Parameter.md +1 -1
  12. data/docs/Class-Variable.md +2 -2
  13. data/docs/Command-Line-Options.md +2 -2
  14. data/docs/Control-Couple.md +3 -3
  15. data/docs/Control-Parameter.md +2 -2
  16. data/docs/Data-Clump.md +2 -2
  17. data/docs/Duplicate-Method-Call.md +4 -4
  18. data/docs/Feature-Envy.md +2 -2
  19. data/docs/How-reek-works-internally.md +2 -2
  20. data/docs/Irresponsible-Module.md +2 -2
  21. data/docs/Large-Class.md +2 -2
  22. data/docs/Long-Parameter-List.md +1 -1
  23. data/docs/Long-Yield-List.md +2 -2
  24. data/docs/Module-Initialize.md +3 -3
  25. data/docs/Nested-Iterators.md +1 -1
  26. data/docs/Nil-Check.md +2 -2
  27. data/docs/Prima-Donna-Method.md +4 -4
  28. data/docs/RSpec-matchers.md +7 -7
  29. data/docs/Rake-Task.md +2 -2
  30. data/docs/Reek-Driven-Development.md +4 -4
  31. data/docs/Repeated-Conditional.md +2 -2
  32. data/docs/Simulated-Polymorphism.md +2 -2
  33. data/docs/Smell-Suppression.md +3 -3
  34. data/docs/Too-Many-Instance-Variables.md +4 -4
  35. data/docs/Too-Many-Methods.md +5 -5
  36. data/docs/Too-Many-Statements.md +2 -2
  37. data/docs/Uncommunicative-Method-Name.md +4 -4
  38. data/docs/Uncommunicative-Module-Name.md +4 -4
  39. data/docs/Uncommunicative-Name.md +2 -2
  40. data/docs/Uncommunicative-Parameter-Name.md +4 -4
  41. data/docs/Uncommunicative-Variable-Name.md +3 -3
  42. data/docs/Unused-Parameters.md +2 -2
  43. data/docs/Utility-Function.md +4 -4
  44. data/docs/Versioning-Policy.md +2 -2
  45. data/features/command_line_interface/options.feature +1 -1
  46. data/features/configuration_files/directory_specific_directives.feature +4 -4
  47. data/features/configuration_loading.feature +10 -24
  48. data/features/programmatic_access.feature +3 -3
  49. data/features/reports/json.feature +1 -1
  50. data/features/reports/reports.feature +2 -2
  51. data/features/reports/yaml.feature +1 -1
  52. data/lib/reek/ast/sexp_extensions.rb +17 -498
  53. data/lib/reek/ast/sexp_extensions/arguments.rb +101 -0
  54. data/lib/reek/ast/sexp_extensions/attribute_assignments.rb +12 -0
  55. data/lib/reek/ast/sexp_extensions/block.rb +36 -0
  56. data/lib/reek/ast/sexp_extensions/case.rb +20 -0
  57. data/lib/reek/ast/sexp_extensions/constant.rb +12 -0
  58. data/lib/reek/ast/sexp_extensions/if.rb +16 -0
  59. data/lib/reek/ast/sexp_extensions/literal.rb +12 -0
  60. data/lib/reek/ast/sexp_extensions/logical_operators.rb +26 -0
  61. data/lib/reek/ast/sexp_extensions/methods.rb +114 -0
  62. data/lib/reek/ast/sexp_extensions/module.rb +85 -0
  63. data/lib/reek/ast/sexp_extensions/nested_assignables.rb +23 -0
  64. data/lib/reek/ast/sexp_extensions/send.rb +60 -0
  65. data/lib/reek/ast/sexp_extensions/super.rb +14 -0
  66. data/lib/reek/ast/sexp_extensions/symbols.rb +16 -0
  67. data/lib/reek/ast/sexp_extensions/variables.rb +38 -0
  68. data/lib/reek/ast/sexp_extensions/when.rb +16 -0
  69. data/lib/reek/ast/sexp_extensions/yield.rb +16 -0
  70. data/lib/reek/cli/application.rb +0 -4
  71. data/lib/reek/cli/options.rb +2 -4
  72. data/lib/reek/configuration/app_configuration.rb +37 -9
  73. data/lib/reek/configuration/configuration_file_finder.rb +8 -5
  74. data/lib/reek/configuration/directory_directives.rb +2 -2
  75. data/lib/reek/context/attribute_context.rb +21 -0
  76. data/lib/reek/context/code_context.rb +5 -9
  77. data/lib/reek/rake/task.rb +5 -5
  78. data/lib/reek/smells/nested_iterators.rb +73 -26
  79. data/lib/reek/smells/smell_warning.rb +1 -38
  80. data/lib/reek/source/source_code.rb +1 -1
  81. data/lib/reek/spec.rb +2 -2
  82. data/lib/reek/spec/should_reek_of.rb +8 -3
  83. data/lib/reek/spec/should_reek_only_of.rb +2 -1
  84. data/lib/reek/spec/smell_matcher.rb +59 -0
  85. data/lib/reek/tree_walker.rb +4 -3
  86. data/lib/reek/version.rb +1 -1
  87. data/logo/reek.bw.png +0 -0
  88. data/logo/reek.bw.svg +77 -0
  89. data/logo/reek.png +0 -0
  90. data/logo/reek.svg +621 -0
  91. data/logo/reek.text.png +0 -0
  92. data/logo/reek.text.svg +628 -0
  93. data/reek.gemspec +1 -1
  94. data/spec/factories/factories.rb +0 -1
  95. data/spec/reek/ast/sexp_extensions_spec.rb +0 -7
  96. data/spec/reek/cli/options_spec.rb +1 -2
  97. data/spec/reek/configuration/app_configuration_spec.rb +30 -14
  98. data/spec/reek/configuration/configuration_file_finder_spec.rb +23 -5
  99. data/spec/reek/smells/attribute_spec.rb +11 -2
  100. data/spec/reek/smells/boolean_parameter_spec.rb +14 -12
  101. data/spec/reek/smells/class_variable_spec.rb +18 -15
  102. data/spec/reek/smells/control_parameter_spec.rb +1 -2
  103. data/spec/reek/smells/duplicate_method_call_spec.rb +1 -2
  104. data/spec/reek/smells/feature_envy_spec.rb +8 -29
  105. data/spec/reek/smells/irresponsible_module_spec.rb +1 -2
  106. data/spec/reek/smells/long_parameter_list_spec.rb +1 -2
  107. data/spec/reek/smells/long_yield_list_spec.rb +1 -2
  108. data/spec/reek/smells/nested_iterators_spec.rb +1 -2
  109. data/spec/reek/smells/nil_check_spec.rb +1 -1
  110. data/spec/reek/smells/prima_donna_method_spec.rb +1 -1
  111. data/spec/reek/smells/repeated_conditional_spec.rb +1 -2
  112. data/spec/reek/smells/smell_detector_shared.rb +1 -1
  113. data/spec/reek/smells/smell_warning_spec.rb +2 -4
  114. data/spec/reek/smells/too_many_instance_variables_spec.rb +20 -19
  115. data/spec/reek/smells/too_many_statements_spec.rb +1 -1
  116. data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -4
  117. data/spec/reek/smells/uncommunicative_module_name_spec.rb +1 -4
  118. data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +1 -4
  119. data/spec/reek/smells/uncommunicative_variable_name_spec.rb +3 -3
  120. data/spec/reek/smells/utility_function_spec.rb +1 -3
  121. data/spec/reek/spec/should_reek_of_spec.rb +5 -5
  122. data/spec/reek/spec/smell_matcher_spec.rb +92 -0
  123. data/tasks/configuration.rake +15 -0
  124. metadata +37 -5
  125. data/config/cucumber.yml +0 -3
  126. data/tasks/develop.rake +0 -21
@@ -0,0 +1,23 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Base module for utility methods for nodes that can contain argument
5
+ # nodes nested through :mlhs nodes.
6
+ module NestedAssignables
7
+ def components
8
+ children.flat_map(&:components)
9
+ end
10
+ end
11
+
12
+ # Utility methods for :args nodes.
13
+ module ArgsNode
14
+ include NestedAssignables
15
+ end
16
+
17
+ # Utility methods for :mlhs nodes.
18
+ module MlhsNode
19
+ include NestedAssignables
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Utility methods for :send nodes.
5
+ module SendNode
6
+ VISIBILITY_MODIFIERS = [:private, :public, :protected, :module_function]
7
+ ATTR_DEFN_METHODS = [:attr_writer, :attr_accessor]
8
+
9
+ def receiver
10
+ children.first
11
+ end
12
+
13
+ def method_name
14
+ children[1]
15
+ end
16
+
17
+ def args
18
+ children[2..-1]
19
+ end
20
+
21
+ def participants
22
+ ([receiver] + args).compact
23
+ end
24
+
25
+ def arg_names
26
+ args.map { |arg| arg.children.first }
27
+ end
28
+
29
+ def module_creation_call?
30
+ object_creation_call? && module_creation_receiver?
31
+ end
32
+
33
+ def module_creation_receiver?
34
+ receiver && [:Class, :Struct].include?(receiver.simple_name)
35
+ end
36
+
37
+ def object_creation_call?
38
+ method_name == :new
39
+ end
40
+
41
+ def visibility_modifier?
42
+ VISIBILITY_MODIFIERS.include?(method_name)
43
+ end
44
+
45
+ def attribute_writer?
46
+ ATTR_DEFN_METHODS.include?(method_name) ||
47
+ attr_with_writable_flag?
48
+ end
49
+
50
+ # Handles the case where we create an attribute writer via:
51
+ # attr :foo, true
52
+ def attr_with_writable_flag?
53
+ method_name == :attr && args.any? && args.last.type == :true
54
+ end
55
+ end
56
+
57
+ Op_AsgnNode = SendNode
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Utility methods for :super nodes.
5
+ module SuperNode
6
+ def method_name
7
+ :super
8
+ end
9
+ end
10
+
11
+ ZsuperNode = SuperNode
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Utility methods for :sym nodes.
5
+ module SymNode
6
+ def name
7
+ children.first
8
+ end
9
+
10
+ def full_name(outer)
11
+ "#{outer}##{name}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Base module for utility methods for nodes representing variables.
5
+ module VariableBase
6
+ def name
7
+ children.first
8
+ end
9
+ end
10
+
11
+ # Utility methods for :cvar nodes.
12
+ module CvarNode
13
+ include VariableBase
14
+ end
15
+
16
+ # Utility methods for :ivar nodes.
17
+ module IvarNode
18
+ include VariableBase
19
+ end
20
+
21
+ # Utility methods for :ivasgn nodes.
22
+ module IvasgnNode
23
+ include VariableBase
24
+ end
25
+
26
+ # Utility methods for :lvar nodes.
27
+ module LvarNode
28
+ include VariableBase
29
+
30
+ alias_method :var_name, :name
31
+ end
32
+
33
+ LvasgnNode = LvarNode
34
+ CvasgnNode = CvarNode
35
+ CvdeclNode = CvarNode
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Utility methods for :when nodes.
5
+ module WhenNode
6
+ def condition_list
7
+ children[0..-2]
8
+ end
9
+
10
+ def body
11
+ children.last
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Reek
2
+ module AST
3
+ module SexpExtensions
4
+ # Utility methods for :yield nodes.
5
+ module YieldNode
6
+ def args
7
+ children
8
+ end
9
+
10
+ def arg_names
11
+ args.map { |arg| arg[1] }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -32,10 +32,6 @@ module Reek
32
32
  status
33
33
  end
34
34
 
35
- def output(text)
36
- print text
37
- end
38
-
39
35
  def report_success
40
36
  self.status = STATUS_SUCCESS
41
37
  end
@@ -12,15 +12,13 @@ module Reek
12
12
  # :reek:TooManyInstanceVariables: { max_instance_variables: 6 }
13
13
  # :reek:Attribute: { enabled: false }
14
14
  class Options
15
- attr_accessor :argv,
16
- :colored,
15
+ attr_reader :argv, :parser, :smells_to_detect
16
+ attr_accessor :colored,
17
17
  :config_file,
18
18
  :location_format,
19
- :parser,
20
19
  :report_format,
21
20
  :show_empty,
22
21
  :show_links,
23
- :smells_to_detect,
24
22
  :sorting
25
23
 
26
24
  def initialize(argv = [])
@@ -9,8 +9,9 @@ require_relative './excluded_paths'
9
9
  module Reek
10
10
  module Configuration
11
11
  #
12
- # Reek's singleton configuration instance.
12
+ # Reek's application configuration.
13
13
  #
14
+ # @public
14
15
  class AppConfiguration
15
16
  include ConfigurationValidator
16
17
  EXCLUDE_PATHS_KEY = 'exclude_paths'
@@ -21,6 +22,8 @@ module Reek
21
22
  # @param path [Pathname] the path to the config file
22
23
  #
23
24
  # @return [AppConfiguration]
25
+ #
26
+ # @public
24
27
  def self.from_path(path = nil)
25
28
  allocate.tap do |instance|
26
29
  instance.instance_eval { find_and_load(path: path) }
@@ -29,16 +32,24 @@ module Reek
29
32
 
30
33
  # Instantiate a configuration by passing everything in.
31
34
  #
32
- # @param map [Hash] can have the following 3 keys:
33
- # 1.) directory_directives [Hash] for instance:
34
- # { Pathname("spec/samples/three_clean_files/") =>
35
- # { Reek::Smells::UtilityFunction => { "enabled" => false } } }
36
- # 2.) default_directive [Hash] for instance:
37
- # { Reek::Smells::IrresponsibleModule => { "enabled" => false } }
38
- # 3.) excluded_paths [Array] for instance:
39
- # [ Pathname('spec/samples/two_smelly_files') ]
35
+ # @deprecated This method will be removed in Reek 4.0.
36
+ #
37
+ # @param [Hash] map a hash with three possible keys representing
38
+ # different types of directives.
39
+ # @option map [Hash] :directory_directives Directory specific configuration
40
+ # for instance:
41
+ # { Pathname("spec/samples/three_clean_files/") =>
42
+ # { Reek::Smells::UtilityFunction => { "enabled" => false } } }
43
+ # @option map [Hash] :default_directive Default configuration
44
+ # for instance:
45
+ # { Reek::Smells::IrresponsibleModule => { "enabled" => false } }
46
+ # @option map [Array] :excluded_paths list of paths to exclude from analysis
47
+ # for instance:
48
+ # [ Pathname('spec/samples/two_smelly_files') ]
40
49
  #
41
50
  # @return [AppConfiguration]
51
+ #
52
+ # @public
42
53
  def self.from_map(map = {})
43
54
  allocate.tap do |instance|
44
55
  instance.instance_eval do
@@ -49,6 +60,23 @@ module Reek
49
60
  end
50
61
  end
51
62
 
63
+ # Instantiate a configuration by passing everything in.
64
+ #
65
+ # Loads the configuration from a hash of the form that is loaded from a
66
+ # +.reek+ config file.
67
+ # @param [Hash] hash The configuration hash to load.
68
+ #
69
+ # @return [AppConfiguration]
70
+ #
71
+ # @public
72
+ def self.from_hash(hash = {})
73
+ allocate.tap do |instance|
74
+ instance.instance_eval do
75
+ load_values hash
76
+ end
77
+ end
78
+ end
79
+
52
80
  def self.default
53
81
  from_path nil
54
82
  end
@@ -5,7 +5,7 @@ module Reek
5
5
  # Raised when config file is not properly readable.
6
6
  class ConfigFileException < StandardError; end
7
7
  #
8
- # ConfigurationFileFinder is responsible for finding reek's configuration.
8
+ # ConfigurationFileFinder is responsible for finding Reek's configuration.
9
9
  #
10
10
  # There are 3 ways of passing `reek` a configuration file:
11
11
  # 1. Using the cli "-c" switch
@@ -24,18 +24,21 @@ module Reek
24
24
 
25
25
  # :reek:ControlParameter
26
26
  def find(path: nil, current: Pathname.pwd, home: Pathname.new(Dir.home))
27
- path || find_by_dir(current) || find_by_dir(home)
27
+ path || find_by_dir(current) || find_in_dir(home)
28
28
  end
29
29
 
30
- # :reek:NestedIterators: { max_allowed_nesting: 2 }
31
30
  def find_by_dir(start)
32
31
  start.ascend do |dir|
33
- files = dir.children.select(&:file?).sort
34
- found = files.find { |file| file.to_s.end_with?('.reek') }
32
+ found = find_in_dir(dir)
35
33
  return found if found
36
34
  end
37
35
  end
38
36
 
37
+ def find_in_dir(dir)
38
+ files = dir.children.select(&:file?).sort
39
+ files.find { |file| file.to_s.end_with?('.reek') }
40
+ end
41
+
39
42
  # :reek:TooManyStatements: { max_statements: 6 }
40
43
  def load_from_file(path)
41
44
  return {} unless path
@@ -50,8 +50,8 @@ module Reek
50
50
 
51
51
  def error_message_for_invalid_smell_type(klass)
52
52
  "You are trying to configure smell type #{klass} but we can't find one with that name.\n" \
53
- "Please make sure you spelled it right (see 'config/defaults.reek' in the reek\n" \
54
- 'repository for a list of all available smell types.'
53
+ "Please make sure you spelled it right. (See 'defaults.reek' in the Reek\n" \
54
+ 'repository for a list of all available smell types.)'
55
55
  end
56
56
  end
57
57
  end
@@ -0,0 +1,21 @@
1
+ require_relative 'code_context'
2
+
3
+ module Reek
4
+ module Context
5
+ #
6
+ # A context wrapper for attribute definitions found in a syntax tree.
7
+ #
8
+ class AttributeContext < CodeContext
9
+ def initialize(context, exp, send_expression)
10
+ @send_expression = send_expression
11
+ super context, exp
12
+ end
13
+
14
+ def full_comment
15
+ send_expression.full_comment || ''
16
+ end
17
+
18
+ private_attr_reader :send_expression
19
+ end
20
+ end
21
+ end
@@ -117,10 +117,6 @@ module Reek
117
117
  end
118
118
  end
119
119
 
120
- def num_methods
121
- 0
122
- end
123
-
124
120
  def full_name
125
121
  exp.full_name(context ? context.full_name : '')
126
122
  end
@@ -180,11 +176,11 @@ module Reek
180
176
  end
181
177
 
182
178
  def config
183
- @config ||= if exp
184
- CodeComment.new(exp.full_comment || '').config
185
- else
186
- {}
187
- end
179
+ @config ||= CodeComment.new(full_comment).config
180
+ end
181
+
182
+ def full_comment
183
+ exp.full_comment || ''
188
184
  end
189
185
 
190
186
  def context_config_for(detector_class)
@@ -8,13 +8,13 @@ require 'English'
8
8
 
9
9
  module Reek
10
10
  #
11
- # Defines a task library for running reek.
11
+ # Defines a task library for running Reek.
12
12
  # (Classes here will be configured via the Rakefile, and therefore will
13
13
  # possess a :reek:attribute or two.)
14
14
  #
15
15
  # @public
16
16
  module Rake
17
- # A Rake task that runs reek on a set of source files.
17
+ # A Rake task that runs Reek on a set of source files.
18
18
  #
19
19
  # Example:
20
20
  #
@@ -39,11 +39,11 @@ module Reek
39
39
  # :reek:TooManyInstanceVariables: { max_instance_variables: 6 }
40
40
  # :reek:Attribute
41
41
  class Task < ::Rake::TaskLib
42
- # Name of reek task. Defaults to :reek.
42
+ # Name of Reek task. Defaults to :reek.
43
43
  # @public
44
44
  attr_writer :name
45
45
 
46
- # Path to reek's config file.
46
+ # Path to Reek's config file.
47
47
  # Setting the REEK_CFG environment variable overrides this.
48
48
  # @public
49
49
  attr_accessor :config_file
@@ -105,7 +105,7 @@ module Reek
105
105
  def run_task
106
106
  puts "\n\n!!! Running 'reek' rake command: #{command}\n\n" if verbose
107
107
  system(*command)
108
- abort("\n\n!!! `reek` has found smells - exiting!") if sys_call_failed? && fail_on_error
108
+ abort("\n\n!!! Reek has found smells - exiting!") if sys_call_failed? && fail_on_error
109
109
  end
110
110
 
111
111
  def command
@@ -10,6 +10,16 @@ module Reek
10
10
  #
11
11
  # See {file:docs/Nested-Iterators.md} for details.
12
12
  class NestedIterators < SmellDetector
13
+ # Struct for conveniently associating iterators with their depth (that is, their nesting).
14
+ Iterator = Struct.new :exp, :depth do
15
+ include Comparable
16
+ def <=>(other)
17
+ depth <=> other.depth
18
+ end
19
+ end
20
+
21
+ private_attr_accessor :ignore_iterators
22
+
13
23
  # The name of the config field that sets the maximum depth
14
24
  # of nested iterators to be permitted within any single method.
15
25
  MAX_ALLOWED_NESTING_KEY = 'max_allowed_nesting'
@@ -28,47 +38,84 @@ module Reek
28
38
  end
29
39
 
30
40
  #
31
- # Checks whether the given +block+ is inside another.
41
+ # Attempts to find the deepest nested iterator and warns if it's depth
42
+ # is bigger than our allowed maximum.
32
43
  #
33
44
  # @return [Array<SmellWarning>]
34
45
  #
46
+ # :reek:TooManyStatements: { max_statements: 6 }
35
47
  def examine_context(ctx)
36
- exp, depth = *find_deepest_iterator(ctx)
48
+ configure_ignore_iterators(ctx)
49
+ deepest_iterator = find_deepest_iterator ctx
50
+ return [] unless deepest_iterator
51
+ depth = deepest_iterator.depth
52
+ return [] unless depth > max_nesting(ctx)
37
53
 
38
- if depth && depth > value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
39
- [smell_warning(
40
- context: ctx,
41
- lines: [exp.line],
42
- message: "contains iterators nested #{depth} deep",
43
- parameters: { name: ctx.full_name, count: depth })]
44
- else
45
- []
46
- end
54
+ [smell_warning(
55
+ context: ctx,
56
+ lines: [deepest_iterator.exp.line],
57
+ message: "contains iterators nested #{depth} deep",
58
+ parameters: { name: ctx.full_name, count: depth })]
47
59
  end
48
60
 
49
61
  private
50
62
 
51
- private_attr_accessor :ignore_iterators
52
-
63
+ #
64
+ # @return [Iterator|nil]
65
+ #
53
66
  def find_deepest_iterator(ctx)
54
- self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
55
-
56
- find_iters(ctx.exp, 1).sort_by { |item| item[1] }.last
67
+ exp = ctx.exp
68
+ return nil unless exp.find_nodes([:block])
69
+ scout(parent: exp, exp: exp, depth: 0).
70
+ flatten.
71
+ sort.
72
+ last
57
73
  end
58
74
 
59
- def find_iters(exp, depth)
60
- return [] unless exp
61
- exp.find_nodes([:block]).flat_map do |elem|
62
- find_iters_for_iter_node(elem, depth)
75
+ # A little digression into parser's sexp is necessary here:
76
+ #
77
+ # Given
78
+ # foo.each() do ... end
79
+ # this will end up as:
80
+ #
81
+ # "foo.each() do ... end" -> the iterator below
82
+ # "each()" -> the "call" below
83
+ # "do ... end" -> the "block" below
84
+ #
85
+ # @param parent [AST::Node] The parent iterator
86
+ #
87
+ # @param exp [AST::Node]
88
+ # The given expression to analyze.
89
+ # Will be nil on empty blocks so we'll return just the parent iterator
90
+ #
91
+ # @param depth [Integer]
92
+ #
93
+ # @return [Array<Iterator>]
94
+ #
95
+ def scout(parent: raise, exp: raise, depth: raise)
96
+ return [Iterator.new(parent, depth)] unless exp
97
+ iterators = exp.find_nodes([:block])
98
+ return [Iterator.new(parent, depth)] if iterators.empty?
99
+ iterators.map do |iterator|
100
+ # 1st case: we recurse down the given block of the iterator. In this case
101
+ # we need to check if we should increment the depth.
102
+ # 2nd case: we recurse down the associated call of the iterator. In this case
103
+ # the depth stays the same.
104
+ scout(parent: iterator, exp: iterator.block, depth: increment_depth(iterator, depth)) +
105
+ scout(parent: iterator, exp: iterator.call, depth: depth)
63
106
  end
64
107
  end
65
108
 
66
- def find_iters_for_iter_node(exp, depth)
67
- ignored = ignored_iterator? exp
68
- result = find_iters(exp.call, depth) +
69
- find_iters(exp.block, depth + (ignored ? 0 : 1))
70
- result << [exp, depth] unless ignored
71
- result
109
+ def configure_ignore_iterators(ctx)
110
+ self.ignore_iterators = value(IGNORE_ITERATORS_KEY, ctx, DEFAULT_IGNORE_ITERATORS)
111
+ end
112
+
113
+ def increment_depth(iterator, depth)
114
+ ignored_iterator?(iterator) ? depth : depth + 1
115
+ end
116
+
117
+ def max_nesting(ctx)
118
+ value(MAX_ALLOWED_NESTING_KEY, ctx, DEFAULT_MAX_ALLOWED_NESTING)
72
119
  end
73
120
 
74
121
  # :reek:FeatureEnvy