reek 5.0.2 → 5.1.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.
- 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
|