reek 5.0.2 → 5.1.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 +9 -70
- data/.rubocop_todo.yml +63 -0
- data/.simplecov +4 -1
- data/CHANGELOG.md +5 -0
- data/Gemfile +12 -17
- data/README.md +21 -29
- data/Rakefile +2 -2
- data/docs/templates/default/docstring/setup.rb +3 -0
- data/features/command_line_interface/options.feature +5 -3
- data/features/configuration_files/schema_validation.feature +3 -3
- data/features/configuration_files/show_configuration_file.feature +44 -0
- data/features/rake_task/rake_task.feature +1 -1
- data/features/reports/json.feature +3 -3
- data/features/reports/reports.feature +4 -4
- data/features/reports/yaml.feature +3 -3
- data/features/rspec_matcher.feature +1 -0
- data/features/step_definitions/sample_file_steps.rb +6 -8
- data/features/todo_list.feature +39 -26
- data/lib/reek.rb +7 -0
- data/lib/reek/ast/node.rb +4 -0
- data/lib/reek/ast/sexp_extensions/if.rb +20 -0
- data/lib/reek/ast/sexp_extensions/methods.rb +1 -0
- data/lib/reek/cli/application.rb +25 -0
- data/lib/reek/cli/command/todo_list_command.rb +17 -7
- data/lib/reek/cli/options.rb +21 -14
- data/lib/reek/code_comment.rb +2 -0
- data/lib/reek/configuration/app_configuration.rb +0 -3
- data/lib/reek/configuration/configuration_converter.rb +4 -4
- data/lib/reek/configuration/directory_directives.rb +1 -0
- data/lib/reek/configuration/schema_validator.rb +1 -0
- data/lib/reek/context/method_context.rb +1 -0
- data/lib/reek/context/module_context.rb +5 -4
- data/lib/reek/context/visibility_tracker.rb +7 -4
- data/lib/reek/context_builder.rb +1 -0
- data/lib/reek/detector_repository.rb +1 -0
- data/lib/reek/errors/incomprehensible_source_error.rb +2 -2
- data/lib/reek/errors/syntax_error.rb +4 -0
- data/lib/reek/examiner.rb +1 -0
- data/lib/reek/report/text_report.rb +1 -0
- data/lib/reek/smell_detectors/control_parameter.rb +13 -107
- data/lib/reek/smell_detectors/control_parameter_helpers/call_in_condition_finder.rb +91 -0
- data/lib/reek/smell_detectors/control_parameter_helpers/candidate.rb +38 -0
- data/lib/reek/smell_detectors/control_parameter_helpers/control_parameter_finder.rb +94 -0
- data/lib/reek/smell_detectors/duplicate_method_call.rb +1 -0
- data/lib/reek/smell_detectors/feature_envy.rb +2 -0
- data/lib/reek/smell_detectors/irresponsible_module.rb +1 -0
- data/lib/reek/smell_detectors/long_parameter_list.rb +1 -0
- data/lib/reek/smell_detectors/manual_dispatch.rb +1 -0
- data/lib/reek/smell_detectors/missing_safe_method.rb +1 -0
- data/lib/reek/smell_detectors/nested_iterators.rb +1 -0
- data/lib/reek/smell_detectors/repeated_conditional.rb +1 -0
- data/lib/reek/smell_detectors/too_many_instance_variables.rb +1 -0
- data/lib/reek/smell_detectors/too_many_methods.rb +1 -0
- data/lib/reek/smell_detectors/too_many_statements.rb +1 -0
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +2 -0
- data/lib/reek/smell_detectors/unused_parameters.rb +1 -0
- data/lib/reek/source/source_locator.rb +1 -0
- data/lib/reek/spec/should_reek_of.rb +2 -2
- data/lib/reek/spec/should_reek_only_of.rb +1 -0
- data/lib/reek/spec/smell_matcher.rb +1 -0
- data/lib/reek/tree_dresser.rb +1 -0
- data/lib/reek/version.rb +1 -1
- data/samples/smelly_source/ruby.rb +368 -0
- data/spec/factories/factories.rb +10 -9
- data/spec/performance/reek/smell_detectors/runtime_speed_spec.rb +17 -0
- data/spec/quality/documentation_spec.rb +40 -0
- data/spec/reek/ast/sexp_extensions_spec.rb +20 -20
- data/spec/reek/cli/application_spec.rb +29 -0
- data/spec/reek/cli/command/todo_list_command_spec.rb +64 -46
- data/spec/reek/configuration/app_configuration_spec.rb +8 -8
- data/spec/reek/configuration/configuration_file_finder_spec.rb +3 -3
- data/spec/reek/configuration/schema_validator_spec.rb +10 -10
- data/spec/reek/detector_repository_spec.rb +2 -2
- data/spec/reek/smell_detectors/control_parameter_spec.rb +17 -0
- data/spec/reek/source/source_locator_spec.rb +0 -2
- data/spec/spec_helper.rb +2 -0
- data/tasks/configuration.rake +2 -1
- data/tasks/test.rake +4 -0
- metadata +11 -5
- data/ataru_setup.rb +0 -13
- data/tasks/ataru.rake +0 -5
data/lib/reek/code_comment.rb
CHANGED
@@ -122,6 +122,7 @@ module Reek
|
|
122
122
|
|
123
123
|
def escalate_bad_detector
|
124
124
|
return if SmellDetectors::BaseDetector.valid_detector?(detector_name)
|
125
|
+
|
125
126
|
raise Errors::BadDetectorInCommentError, detector_name: detector_name,
|
126
127
|
original_comment: original_comment,
|
127
128
|
source: source,
|
@@ -142,6 +143,7 @@ module Reek
|
|
142
143
|
@detector_class = SmellDetectors::BaseDetector.to_detector(detector_name)
|
143
144
|
|
144
145
|
return if given_keys_legit?
|
146
|
+
|
145
147
|
raise Errors::BadDetectorConfigurationKeyInCommentError, detector_name: detector_name,
|
146
148
|
offensive_keys: configuration_keys_difference,
|
147
149
|
original_comment: original_comment,
|
@@ -71,9 +71,9 @@ module Reek
|
|
71
71
|
# @quality :reek:NestedIterators { max_allowed_nesting: 3 }
|
72
72
|
# @quality :reek:TooManyStatements { max_statements: 6 }
|
73
73
|
def strings_to_regexes_for_detectors
|
74
|
-
return unless configuration[
|
74
|
+
return unless configuration[DETECTORS_KEY]
|
75
75
|
|
76
|
-
configuration[
|
76
|
+
configuration[DETECTORS_KEY].tap do |detectors|
|
77
77
|
detectors.keys.each do |detector|
|
78
78
|
convertible_attributes(detectors[detector]).each do |attribute|
|
79
79
|
detectors[detector][attribute] = detectors[detector][attribute].map do |item|
|
@@ -91,9 +91,9 @@ module Reek
|
|
91
91
|
# @quality :reek:NestedIterators { max_allowed_nesting: 4 }
|
92
92
|
# @quality :reek:TooManyStatements { max_statements: 7 }
|
93
93
|
def strings_to_regexes_for_directories
|
94
|
-
return unless configuration[
|
94
|
+
return unless configuration[DIRECTORIES_KEY]
|
95
95
|
|
96
|
-
configuration[
|
96
|
+
configuration[DIRECTORIES_KEY].tap do |directories|
|
97
97
|
directories.keys.each do |directory|
|
98
98
|
directories[directory].each do |detector, configuration|
|
99
99
|
convertible_attributes(configuration).each do |attribute|
|
@@ -26,7 +26,7 @@ module Reek
|
|
26
26
|
#
|
27
27
|
# @param child [CodeContext] the child context to register
|
28
28
|
def append_child_context(child)
|
29
|
-
visibility_tracker.
|
29
|
+
visibility_tracker.apply_visibility(child)
|
30
30
|
super
|
31
31
|
end
|
32
32
|
|
@@ -44,9 +44,9 @@ module Reek
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def defined_instance_methods(visibility: :any)
|
47
|
-
instance_method_children
|
48
|
-
|
49
|
-
|
47
|
+
return instance_method_children if visibility == :any
|
48
|
+
|
49
|
+
instance_method_children.select { |child| child.visibility == visibility }
|
50
50
|
end
|
51
51
|
|
52
52
|
def instance_method_calls
|
@@ -76,6 +76,7 @@ module Reek
|
|
76
76
|
# @quality :reek:FeatureEnvy
|
77
77
|
def namespace_module?
|
78
78
|
return false if exp.type == :casgn
|
79
|
+
|
79
80
|
children = exp.direct_children
|
80
81
|
children.any? && children.all? { |child| [:casgn, :class, :module].include? child.type }
|
81
82
|
end
|
@@ -22,6 +22,7 @@ module Reek
|
|
22
22
|
#
|
23
23
|
def track_visibility(children:, visibility:, names:)
|
24
24
|
return unless VISIBILITY_MODIFIERS.include? visibility
|
25
|
+
|
25
26
|
if names.any?
|
26
27
|
children.each do |child|
|
27
28
|
child.visibility = visibility if names.include?(child.name)
|
@@ -44,17 +45,19 @@ module Reek
|
|
44
45
|
#
|
45
46
|
def track_singleton_visibility(children:, visibility:, names:)
|
46
47
|
return if names.empty?
|
48
|
+
|
47
49
|
visibility = VISIBILITY_MAP[visibility]
|
48
50
|
return unless visibility
|
51
|
+
|
49
52
|
track_visibility children: children, visibility: visibility, names: names
|
50
53
|
end
|
51
54
|
|
52
|
-
# Sets the visibility of a
|
55
|
+
# Sets the visibility of a CodeContext to the tracked visibility.
|
53
56
|
#
|
54
|
-
# @param
|
57
|
+
# @param context [CodeContext]
|
55
58
|
#
|
56
|
-
def
|
57
|
-
|
59
|
+
def apply_visibility(context)
|
60
|
+
context.apply_current_visibility tracked_visibility
|
58
61
|
end
|
59
62
|
|
60
63
|
private
|
data/lib/reek/context_builder.rb
CHANGED
@@ -17,8 +17,8 @@ module Reek
|
|
17
17
|
It would be great if you could report this back to the Reek team by opening a
|
18
18
|
corresponding issue at https://github.com/troessner/reek/issues.
|
19
19
|
|
20
|
-
Please make sure to include the source in question, the Reek version
|
21
|
-
original exception below.
|
20
|
+
Please make sure to include the source in question, the Reek version,
|
21
|
+
and this entire error message, including the original exception below.
|
22
22
|
|
23
23
|
Exception message:
|
24
24
|
|
@@ -15,6 +15,10 @@ module Reek
|
|
15
15
|
This is a problem that is outside of Reek's scope and should be fixed by you, the
|
16
16
|
user, in order for Reek being able to continue.
|
17
17
|
|
18
|
+
Things you can try:
|
19
|
+
- Check the syntax of the problematic file
|
20
|
+
- If the file is not in fact a Ruby file, exclude it in your .reek.yml file
|
21
|
+
|
18
22
|
Exception message:
|
19
23
|
|
20
24
|
%<exception>s
|
data/lib/reek/examiner.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'base_detector'
|
4
|
+
require_relative 'control_parameter_helpers/candidate'
|
5
|
+
require_relative 'control_parameter_helpers/control_parameter_finder'
|
4
6
|
|
5
7
|
module Reek
|
6
8
|
module SmellDetectors
|
@@ -61,125 +63,29 @@ module Reek
|
|
61
63
|
|
62
64
|
private
|
63
65
|
|
66
|
+
#
|
67
|
+
# @return [Array<ControlParameterHelpers::Candidate>]
|
68
|
+
#
|
64
69
|
def control_parameters
|
65
70
|
potential_parameters.
|
66
|
-
map { |
|
71
|
+
map { |parameter| ControlParameterHelpers::Candidate.new(parameter, find_matches(parameter)) }.
|
67
72
|
select(&:smells?)
|
68
73
|
end
|
69
74
|
|
75
|
+
#
|
76
|
+
# @return [Array<Symbol>] e.g. [:bravo, :charlie]
|
77
|
+
#
|
70
78
|
def potential_parameters
|
71
79
|
expression.parameter_names
|
72
80
|
end
|
73
81
|
|
74
|
-
def find_matches(param)
|
75
|
-
ControlParameterFinder.new(expression, param).find_matches
|
76
|
-
end
|
77
|
-
|
78
82
|
#
|
79
|
-
#
|
83
|
+
# @param parameter [Symbol] the name of the parameter
|
84
|
+
# @return [Array<Reek::AST::Node>]
|
80
85
|
#
|
81
|
-
|
82
|
-
|
83
|
-
@param = param
|
84
|
-
@occurences = occurences
|
85
|
-
end
|
86
|
-
|
87
|
-
def smells?
|
88
|
-
occurences.any?
|
89
|
-
end
|
90
|
-
|
91
|
-
def lines
|
92
|
-
occurences.map(&:line)
|
93
|
-
end
|
94
|
-
|
95
|
-
def name
|
96
|
-
param.to_s
|
97
|
-
end
|
98
|
-
|
99
|
-
private
|
100
|
-
|
101
|
-
attr_reader :occurences, :param
|
86
|
+
def find_matches(parameter)
|
87
|
+
ControlParameterHelpers::ControlParameterFinder.new(expression, parameter).find_matches
|
102
88
|
end
|
103
|
-
|
104
|
-
private_constant :FoundControlParameter
|
105
|
-
|
106
|
-
# Finds cases of ControlParameter in a particular node for a particular parameter
|
107
|
-
class ControlParameterFinder
|
108
|
-
CONDITIONAL_NODE_TYPES = [:if, :case, :and, :or].freeze
|
109
|
-
|
110
|
-
def initialize(node, param)
|
111
|
-
@node = node
|
112
|
-
@param = param
|
113
|
-
end
|
114
|
-
|
115
|
-
def find_matches
|
116
|
-
return [] if legitimite_uses?
|
117
|
-
nested_finders.flat_map(&:find_matches) + uses_of_param_in_condition
|
118
|
-
end
|
119
|
-
|
120
|
-
def legitimite_uses?
|
121
|
-
return true if uses_param_in_body?
|
122
|
-
return true if uses_param_in_call_in_condition?
|
123
|
-
return true if nested_finders.any?(&:legitimite_uses?)
|
124
|
-
false
|
125
|
-
end
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
attr_reader :node, :param
|
130
|
-
|
131
|
-
def conditional_nodes
|
132
|
-
node.body_nodes(CONDITIONAL_NODE_TYPES)
|
133
|
-
end
|
134
|
-
|
135
|
-
def nested_finders
|
136
|
-
@nested_finders ||= conditional_nodes.flat_map do |node|
|
137
|
-
self.class.new(node, param)
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
def uses_param_in_call_in_condition?
|
142
|
-
return unless condition
|
143
|
-
condition.each_node(:send) do |inner|
|
144
|
-
next unless regular_call_involving_param? inner
|
145
|
-
return true
|
146
|
-
end
|
147
|
-
false
|
148
|
-
end
|
149
|
-
|
150
|
-
def uses_of_param_in_condition
|
151
|
-
return [] unless condition
|
152
|
-
condition.each_node(:lvar).select { |inner| inner.var_name == param }
|
153
|
-
end
|
154
|
-
|
155
|
-
def condition
|
156
|
-
return nil unless CONDITIONAL_NODE_TYPES.include? node.type
|
157
|
-
node.condition
|
158
|
-
end
|
159
|
-
|
160
|
-
def regular_call_involving_param?(call_node)
|
161
|
-
call_involving_param?(call_node) && !comparison_call?(call_node)
|
162
|
-
end
|
163
|
-
|
164
|
-
def comparison_call?(call_node)
|
165
|
-
comparison_method_names.include? call_node.name
|
166
|
-
end
|
167
|
-
|
168
|
-
def comparison_method_names
|
169
|
-
[:==, :!=, :=~]
|
170
|
-
end
|
171
|
-
|
172
|
-
def call_involving_param?(call_node)
|
173
|
-
call_node.each_node(:lvar).any? { |it| it.var_name == param }
|
174
|
-
end
|
175
|
-
|
176
|
-
def uses_param_in_body?
|
177
|
-
nodes = node.body_nodes([:lvar], CONDITIONAL_NODE_TYPES)
|
178
|
-
nodes.any? { |lvar_node| lvar_node.var_name == param }
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
private_constant :ControlParameterFinder
|
183
89
|
end
|
184
90
|
end
|
185
91
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module SmellDetectors
|
5
|
+
module ControlParameterHelpers
|
6
|
+
#
|
7
|
+
# CallInConditionFinder finds usages of the given parameter
|
8
|
+
# in the context of a method call in a condition, e.g.:
|
9
|
+
#
|
10
|
+
# def alfa(bravo)
|
11
|
+
# if charlie(bravo)
|
12
|
+
# delta
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# or
|
17
|
+
#
|
18
|
+
# def alfa(bravo)
|
19
|
+
# if bravo.charlie?
|
20
|
+
# delta
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Those usages are legit and should not trigger the ControlParameter smell warning.
|
25
|
+
#
|
26
|
+
class CallInConditionFinder
|
27
|
+
COMPARISON_METHOD_NAMES = [:==, :!=, :=~].freeze
|
28
|
+
|
29
|
+
#
|
30
|
+
# @param node [Reek::AST::Node] the node in our current scope,
|
31
|
+
# e.g. s(:def, :alfa,
|
32
|
+
# s(:args,
|
33
|
+
# s(:arg, :bravo),
|
34
|
+
# @param parameter [Symbol] the parameter name in question
|
35
|
+
# e.g. in the example above this would be :bravo
|
36
|
+
#
|
37
|
+
def initialize(node, parameter)
|
38
|
+
@node = node
|
39
|
+
@parameter = parameter
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @return [Boolean] if the parameter in question has been used in the context of a
|
44
|
+
# method call in a condition
|
45
|
+
#
|
46
|
+
def uses_param_in_call_in_condition?
|
47
|
+
condition = node.condition
|
48
|
+
return false unless condition
|
49
|
+
|
50
|
+
condition.each_node(:send) do |inner|
|
51
|
+
return true if regular_call_involving_param?(inner)
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :node, :parameter
|
59
|
+
|
60
|
+
#
|
61
|
+
# @return [Boolean] if the parameter is used in a method call that is not a comparison call
|
62
|
+
# e.g. this would return true given that "bravo" is the parameter in question:
|
63
|
+
#
|
64
|
+
# if charlie(bravo) then delta end
|
65
|
+
#
|
66
|
+
# while this would return false (since its a comparison):
|
67
|
+
#
|
68
|
+
# if bravo == charlie then charlie end
|
69
|
+
#
|
70
|
+
def regular_call_involving_param?(call_node)
|
71
|
+
call_involving_param?(call_node) && !comparison_call?(call_node)
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# @return [Boolean] if the parameter is used in the given method call
|
76
|
+
#
|
77
|
+
def call_involving_param?(call_node)
|
78
|
+
call_node.each_node(:lvar).any? { |it| it.var_name == parameter }
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# @return [Boolean] if the given method call is a comparison call
|
83
|
+
#
|
84
|
+
# :reek:UtilityFunction
|
85
|
+
def comparison_call?(call_node)
|
86
|
+
COMPARISON_METHOD_NAMES.include? call_node.name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reek
|
4
|
+
module SmellDetectors
|
5
|
+
module ControlParameterHelpers
|
6
|
+
#
|
7
|
+
# Collects information about a single control parameter.
|
8
|
+
#
|
9
|
+
class Candidate
|
10
|
+
#
|
11
|
+
# @param parameter [Symbol] the parameter name
|
12
|
+
# @param occurences [Array<Reek::AST::Node>] the occurences of the ControlParameter smell
|
13
|
+
# e.g. [s(:lvar, :bravo), s(:lvar, :bravo)]
|
14
|
+
#
|
15
|
+
def initialize(parameter, occurences)
|
16
|
+
@parameter = parameter
|
17
|
+
@occurences = occurences
|
18
|
+
end
|
19
|
+
|
20
|
+
def smells?
|
21
|
+
occurences.any?
|
22
|
+
end
|
23
|
+
|
24
|
+
def lines
|
25
|
+
occurences.map(&:line)
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
parameter.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :occurences, :parameter
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|