Class: Reek::SmellDetectors::NestedIterators
- Inherits:
-
BaseDetector
- Object
- BaseDetector
- Reek::SmellDetectors::NestedIterators
- Defined in:
- lib/reek/smell_detectors/nested_iterators.rb
Overview
A Nested Iterator occurs when a block contains another block.
NestedIterators
reports failing methods only once.
See Nested-Iterators for details.
Defined Under Namespace
Classes: Iterator
Constant Summary collapse
- MAX_ALLOWED_NESTING_KEY =
The name of the config field that sets the maximum depth of nested iterators to be permitted within any single method.
'max_allowed_nesting'
- DEFAULT_MAX_ALLOWED_NESTING =
1
- IGNORE_ITERATORS_KEY =
The name of the config field that sets the names of any methods for which nesting should not be considered
'ignore_iterators'
- DEFAULT_IGNORE_ITERATORS =
['tap', 'Tempfile.create'].freeze
Constants inherited from BaseDetector
BaseDetector::DEFAULT_EXCLUDE_SET, BaseDetector::EXCLUDE_KEY
Instance Attribute Summary
Attributes inherited from BaseDetector
Class Method Summary collapse
Instance Method Summary collapse
-
#find_candidates ⇒ Array<Iterator>
private
Finds the set of independent most deeply nested iterators regardless of nesting depth.
-
#find_violations ⇒ Array<Iterator>
private
Finds the set of independent most deeply nested iterators that are nested more deeply than allowed.
- #ignore_iterators ⇒ Object private
- #ignored_iterator?(exp) ⇒ Boolean private
- #increment_depth(iterator, depth) ⇒ Object private
- #max_allowed_nesting ⇒ Object private
-
#scout(exp:, depth:) ⇒ Array<Iterator>
private
A little digression into parser’s sexp is necessary here:.
-
#sniff ⇒ Array<SmellWarning>
Generates a smell warning for each independent deepest nesting depth that is greater than our allowed maximum.
Methods inherited from BaseDetector
#config_for, configuration_keys, contexts, descendants, #enabled?, #exception?, #expression, inherited, #initialize, #run, #smell_type, smell_type, #smell_warning, #source_line, to_detector, todo_configuration_for, #value
Constructor Details
This class inherits a constructor from Reek::SmellDetectors::BaseDetector
Class Method Details
.default_config ⇒ Object
31 32 33 34 35 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 31 def self.default_config super.merge( MAX_ALLOWED_NESTING_KEY => DEFAULT_MAX_ALLOWED_NESTING, IGNORE_ITERATORS_KEY => DEFAULT_IGNORE_ITERATORS) end |
Instance Method Details
#find_candidates ⇒ Array<Iterator> (private)
Finds the set of independent most deeply nested iterators regardless of nesting depth.
75 76 77 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 75 def find_candidates scout(exp: expression, depth: 0) end |
#find_violations ⇒ Array<Iterator> (private)
Finds the set of independent most deeply nested iterators that are nested more deeply than allowed.
Here, independent means that if iterator A is contained within iterator B, we only include A. But if iterators A and B are both contained in iterator C, but A is not contained in B, nor B in A, both A and B are included.
66 67 68 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 66 def find_violations find_candidates.select { |it| it.depth > max_allowed_nesting } end |
#ignore_iterators ⇒ Object (private)
117 118 119 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 117 def ignore_iterators @ignore_iterators ||= value(IGNORE_ITERATORS_KEY, context) end |
#ignored_iterator?(exp) ⇒ Boolean (private)
130 131 132 133 134 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 130 def ignored_iterator?(exp) ignore_iterators.any? do |pattern| /#{pattern}/ =~ exp.call.format_to_ruby end || exp.without_block_arguments? end |
#increment_depth(iterator, depth) ⇒ Object (private)
121 122 123 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 121 def increment_depth(iterator, depth) ignored_iterator?(iterator) ? depth : depth + 1 end |
#max_allowed_nesting ⇒ Object (private)
125 126 127 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 125 def max_allowed_nesting @max_allowed_nesting ||= value(MAX_ALLOWED_NESTING_KEY, context) end |
#scout(exp:, depth:) ⇒ Array<Iterator> (private)
A little digression into parser’s sexp is necessary here:
Given
foo.each() do ... end
this will end up as:
“foo.each() do … end” -> one of the :block nodes “each()” -> the node’s “call” “do … end” -> the node’s “block”
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 97 def scout(exp:, depth:) return [] unless exp # Find all non-nested blocks in this expression exp.each_node([:block], [:block]).flat_map do |iterator| new_depth = increment_depth(iterator, depth) # 1st case: we recurse down the given block of the iterator. In this case # we need to check if we should increment the depth. # 2nd case: we recurse down the associated call of the iterator. In this case # the depth stays the same. nested_iterators = scout(exp: iterator.block, depth: new_depth) + scout(exp: iterator.call, depth: depth) if nested_iterators.empty? Iterator.new(iterator, new_depth) else nested_iterators end end end |
#sniff ⇒ Array<SmellWarning>
Generates a smell warning for each independent deepest nesting depth that is greater than our allowed maximum. This means if two iterators with the same depth were found, we combine them into one warning and merge the line information.
44 45 46 47 48 49 50 51 52 |
# File 'lib/reek/smell_detectors/nested_iterators.rb', line 44 def sniff find_violations.group_by(&:depth).map do |depth, group| lines = group.map(&:line) smell_warning( lines: lines, message: "contains iterators nested #{depth} deep", parameters: { depth: depth }) end end |