reek 3.5.0 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +19 -12
- data/CONTRIBUTING.md +7 -7
- data/README.md +91 -28
- data/ataru_setup.rb +13 -0
- data/{config/defaults.reek → defaults.reek} +0 -0
- data/docs/API.md +32 -31
- data/docs/Attribute.md +1 -1
- data/docs/Basic-Smell-Options.md +2 -1
- data/docs/Boolean-Parameter.md +1 -1
- data/docs/Class-Variable.md +2 -2
- data/docs/Command-Line-Options.md +2 -2
- data/docs/Control-Couple.md +3 -3
- data/docs/Control-Parameter.md +2 -2
- data/docs/Data-Clump.md +2 -2
- data/docs/Duplicate-Method-Call.md +4 -4
- data/docs/Feature-Envy.md +2 -2
- data/docs/How-reek-works-internally.md +2 -2
- data/docs/Irresponsible-Module.md +2 -2
- data/docs/Large-Class.md +2 -2
- data/docs/Long-Parameter-List.md +1 -1
- data/docs/Long-Yield-List.md +2 -2
- data/docs/Module-Initialize.md +3 -3
- data/docs/Nested-Iterators.md +1 -1
- data/docs/Nil-Check.md +2 -2
- data/docs/Prima-Donna-Method.md +4 -4
- data/docs/RSpec-matchers.md +7 -7
- data/docs/Rake-Task.md +2 -2
- data/docs/Reek-Driven-Development.md +4 -4
- data/docs/Repeated-Conditional.md +2 -2
- data/docs/Simulated-Polymorphism.md +2 -2
- data/docs/Smell-Suppression.md +3 -3
- data/docs/Too-Many-Instance-Variables.md +4 -4
- data/docs/Too-Many-Methods.md +5 -5
- data/docs/Too-Many-Statements.md +2 -2
- data/docs/Uncommunicative-Method-Name.md +4 -4
- data/docs/Uncommunicative-Module-Name.md +4 -4
- data/docs/Uncommunicative-Name.md +2 -2
- data/docs/Uncommunicative-Parameter-Name.md +4 -4
- data/docs/Uncommunicative-Variable-Name.md +3 -3
- data/docs/Unused-Parameters.md +2 -2
- data/docs/Utility-Function.md +4 -4
- data/docs/Versioning-Policy.md +2 -2
- data/features/command_line_interface/options.feature +1 -1
- data/features/configuration_files/directory_specific_directives.feature +4 -4
- data/features/configuration_loading.feature +10 -24
- data/features/programmatic_access.feature +3 -3
- data/features/reports/json.feature +1 -1
- data/features/reports/reports.feature +2 -2
- data/features/reports/yaml.feature +1 -1
- data/lib/reek/ast/sexp_extensions.rb +17 -498
- data/lib/reek/ast/sexp_extensions/arguments.rb +101 -0
- data/lib/reek/ast/sexp_extensions/attribute_assignments.rb +12 -0
- data/lib/reek/ast/sexp_extensions/block.rb +36 -0
- data/lib/reek/ast/sexp_extensions/case.rb +20 -0
- data/lib/reek/ast/sexp_extensions/constant.rb +12 -0
- data/lib/reek/ast/sexp_extensions/if.rb +16 -0
- data/lib/reek/ast/sexp_extensions/literal.rb +12 -0
- data/lib/reek/ast/sexp_extensions/logical_operators.rb +26 -0
- data/lib/reek/ast/sexp_extensions/methods.rb +114 -0
- data/lib/reek/ast/sexp_extensions/module.rb +85 -0
- data/lib/reek/ast/sexp_extensions/nested_assignables.rb +23 -0
- data/lib/reek/ast/sexp_extensions/send.rb +60 -0
- data/lib/reek/ast/sexp_extensions/super.rb +14 -0
- data/lib/reek/ast/sexp_extensions/symbols.rb +16 -0
- data/lib/reek/ast/sexp_extensions/variables.rb +38 -0
- data/lib/reek/ast/sexp_extensions/when.rb +16 -0
- data/lib/reek/ast/sexp_extensions/yield.rb +16 -0
- data/lib/reek/cli/application.rb +0 -4
- data/lib/reek/cli/options.rb +2 -4
- data/lib/reek/configuration/app_configuration.rb +37 -9
- data/lib/reek/configuration/configuration_file_finder.rb +8 -5
- data/lib/reek/configuration/directory_directives.rb +2 -2
- data/lib/reek/context/attribute_context.rb +21 -0
- data/lib/reek/context/code_context.rb +5 -9
- data/lib/reek/rake/task.rb +5 -5
- data/lib/reek/smells/nested_iterators.rb +73 -26
- data/lib/reek/smells/smell_warning.rb +1 -38
- data/lib/reek/source/source_code.rb +1 -1
- data/lib/reek/spec.rb +2 -2
- data/lib/reek/spec/should_reek_of.rb +8 -3
- data/lib/reek/spec/should_reek_only_of.rb +2 -1
- data/lib/reek/spec/smell_matcher.rb +59 -0
- data/lib/reek/tree_walker.rb +4 -3
- data/lib/reek/version.rb +1 -1
- data/logo/reek.bw.png +0 -0
- data/logo/reek.bw.svg +77 -0
- data/logo/reek.png +0 -0
- data/logo/reek.svg +621 -0
- data/logo/reek.text.png +0 -0
- data/logo/reek.text.svg +628 -0
- data/reek.gemspec +1 -1
- data/spec/factories/factories.rb +0 -1
- data/spec/reek/ast/sexp_extensions_spec.rb +0 -7
- data/spec/reek/cli/options_spec.rb +1 -2
- data/spec/reek/configuration/app_configuration_spec.rb +30 -14
- data/spec/reek/configuration/configuration_file_finder_spec.rb +23 -5
- data/spec/reek/smells/attribute_spec.rb +11 -2
- data/spec/reek/smells/boolean_parameter_spec.rb +14 -12
- data/spec/reek/smells/class_variable_spec.rb +18 -15
- data/spec/reek/smells/control_parameter_spec.rb +1 -2
- data/spec/reek/smells/duplicate_method_call_spec.rb +1 -2
- data/spec/reek/smells/feature_envy_spec.rb +8 -29
- data/spec/reek/smells/irresponsible_module_spec.rb +1 -2
- data/spec/reek/smells/long_parameter_list_spec.rb +1 -2
- data/spec/reek/smells/long_yield_list_spec.rb +1 -2
- data/spec/reek/smells/nested_iterators_spec.rb +1 -2
- data/spec/reek/smells/nil_check_spec.rb +1 -1
- data/spec/reek/smells/prima_donna_method_spec.rb +1 -1
- data/spec/reek/smells/repeated_conditional_spec.rb +1 -2
- data/spec/reek/smells/smell_detector_shared.rb +1 -1
- data/spec/reek/smells/smell_warning_spec.rb +2 -4
- data/spec/reek/smells/too_many_instance_variables_spec.rb +20 -19
- data/spec/reek/smells/too_many_statements_spec.rb +1 -1
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -4
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +1 -4
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +1 -4
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +3 -3
- data/spec/reek/smells/utility_function_spec.rb +1 -3
- data/spec/reek/spec/should_reek_of_spec.rb +5 -5
- data/spec/reek/spec/smell_matcher_spec.rb +92 -0
- data/tasks/configuration.rake +15 -0
- metadata +37 -5
- data/config/cucumber.yml +0 -3
- 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,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
|
data/lib/reek/cli/application.rb
CHANGED
data/lib/reek/cli/options.rb
CHANGED
@@ -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
|
-
|
16
|
-
|
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
|
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
|
-
# @
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
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
|
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) ||
|
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
|
-
|
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 (
|
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 ||=
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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)
|
data/lib/reek/rake/task.rb
CHANGED
@@ -8,13 +8,13 @@ require 'English'
|
|
8
8
|
|
9
9
|
module Reek
|
10
10
|
#
|
11
|
-
# Defines a task library for running
|
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
|
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
|
42
|
+
# Name of Reek task. Defaults to :reek.
|
43
43
|
# @public
|
44
44
|
attr_writer :name
|
45
45
|
|
46
|
-
# Path to
|
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!!!
|
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
|
-
#
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
52
|
-
|
63
|
+
#
|
64
|
+
# @return [Iterator|nil]
|
65
|
+
#
|
53
66
|
def find_deepest_iterator(ctx)
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|