reek 5.6.0 → 6.0.3
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/.github/dependabot.yml +9 -0
- data/.github/workflows/ruby.yml +52 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +3 -1
- data/.rubocop_todo.yml +27 -20
- data/.simplecov +1 -0
- data/CHANGELOG.md +24 -0
- data/Dockerfile +1 -0
- data/Gemfile +14 -17
- data/README.md +11 -11
- data/bin/code_climate_reek +12 -2
- data/docs/Attribute.md +1 -1
- data/docs/Boolean-Parameter.md +2 -2
- data/docs/Control-Couple.md +1 -1
- data/docs/Nil-Check.md +4 -1
- data/docs/templates/default/docstring/setup.rb +1 -3
- data/features/command_line_interface/options.feature +2 -3
- data/features/configuration_files/schema_validation.feature +1 -1
- data/features/reports/codeclimate.feature +2 -2
- data/features/reports/json.feature +3 -3
- data/features/reports/reports.feature +4 -4
- data/features/reports/yaml.feature +3 -3
- data/features/step_definitions/reek_steps.rb +5 -1
- data/features/step_definitions/sample_file_steps.rb +2 -2
- data/features/support/env.rb +0 -1
- data/lib/reek/ast/node.rb +1 -1
- data/lib/reek/ast/sexp_extensions/arguments.rb +11 -0
- data/lib/reek/cli/options.rb +3 -3
- data/lib/reek/code_comment.rb +36 -29
- data/lib/reek/configuration/app_configuration.rb +4 -3
- data/lib/reek/configuration/configuration_converter.rb +2 -2
- data/lib/reek/configuration/directory_directives.rb +9 -3
- data/lib/reek/configuration/excluded_paths.rb +2 -1
- data/lib/reek/context/code_context.rb +1 -1
- data/lib/reek/context/module_context.rb +3 -1
- data/lib/reek/context/refinement_context.rb +16 -0
- data/lib/reek/context_builder.rb +16 -2
- data/lib/reek/errors/legacy_comment_separator_error.rb +36 -0
- data/lib/reek/report/code_climate/code_climate_configuration.yml +1 -1
- data/lib/reek/report/code_climate/code_climate_report.rb +2 -1
- data/lib/reek/report/simple_warning_formatter.rb +0 -7
- data/lib/reek/report.rb +5 -7
- data/lib/reek/smell_detectors/base_detector.rb +1 -9
- data/lib/reek/smell_detectors/boolean_parameter.rb +3 -1
- data/lib/reek/smell_detectors/data_clump.rb +23 -56
- data/lib/reek/smell_detectors/nil_check.rb +1 -12
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +1 -1
- data/lib/reek/smell_warning.rb +1 -2
- data/lib/reek/source/source_locator.rb +13 -10
- data/lib/reek/spec/smell_matcher.rb +2 -1
- data/lib/reek/version.rb +1 -1
- data/lib/reek.rb +1 -0
- data/reek.gemspec +13 -6
- data/spec/performance/reek/smell_detectors/runtime_speed_spec.rb +2 -4
- data/spec/quality/documentation_spec.rb +2 -1
- data/spec/reek/ast/sexp_extensions_spec.rb +15 -33
- data/spec/reek/code_comment_spec.rb +41 -42
- data/spec/reek/configuration/directory_directives_spec.rb +6 -0
- data/spec/reek/configuration/excluded_paths_spec.rb +12 -3
- data/spec/reek/context_builder_spec.rb +110 -113
- data/spec/reek/report/code_climate/code_climate_configuration_spec.rb +1 -3
- data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +26 -26
- data/spec/reek/report/code_climate/code_climate_formatter_spec.rb +6 -6
- data/spec/reek/report/location_formatter_spec.rb +3 -3
- data/spec/reek/smell_detectors/base_detector_spec.rb +3 -13
- data/spec/reek/smell_detectors/data_clump_spec.rb +14 -0
- data/spec/reek/smell_detectors/missing_safe_method_spec.rb +8 -2
- data/spec/reek/smell_detectors/nil_check_spec.rb +3 -3
- data/spec/reek/smell_detectors/utility_function_spec.rb +16 -0
- data/spec/reek/smell_warning_spec.rb +12 -12
- data/spec/reek/source/source_code_spec.rb +13 -0
- data/spec/reek/spec/should_reek_of_spec.rb +0 -1
- data/spec/reek/spec/should_reek_only_of_spec.rb +6 -6
- data/spec/reek/spec/smell_matcher_spec.rb +1 -1
- data/spec/spec_helper.rb +19 -5
- data/tasks/configuration.rake +1 -2
- metadata +24 -42
- data/.travis.yml +0 -35
- data/spec/factories/factories.rb +0 -37
data/lib/reek/cli/options.rb
CHANGED
|
@@ -87,7 +87,7 @@ module Reek
|
|
|
87
87
|
|
|
88
88
|
def set_banner
|
|
89
89
|
program_name = parser.program_name
|
|
90
|
-
parser.banner = <<-BANNER.gsub(/^
|
|
90
|
+
parser.banner = <<-BANNER.gsub(/^ +/, '')
|
|
91
91
|
Usage: #{program_name} [options] [files]
|
|
92
92
|
|
|
93
93
|
Examples:
|
|
@@ -131,9 +131,9 @@ module Reek
|
|
|
131
131
|
def set_alternative_formatter_options
|
|
132
132
|
parser.separator "\nReport format:"
|
|
133
133
|
parser.on(
|
|
134
|
-
'-f', '--format FORMAT', [:html, :text, :yaml, :json, :xml
|
|
134
|
+
'-f', '--format FORMAT', [:html, :text, :yaml, :json, :xml],
|
|
135
135
|
'Report smells in the given format:',
|
|
136
|
-
' html', ' text (default)', ' yaml', ' json', ' xml'
|
|
136
|
+
' html', ' text (default)', ' yaml', ' json', ' xml') do |opt|
|
|
137
137
|
self.report_format = opt
|
|
138
138
|
end
|
|
139
139
|
end
|
data/lib/reek/code_comment.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative 'smell_detectors/base_detector'
|
|
|
6
6
|
require_relative 'errors/bad_detector_in_comment_error'
|
|
7
7
|
require_relative 'errors/bad_detector_configuration_key_in_comment_error'
|
|
8
8
|
require_relative 'errors/garbage_detector_configuration_in_comment_error'
|
|
9
|
+
require_relative 'errors/legacy_comment_separator_error'
|
|
9
10
|
|
|
10
11
|
module Reek
|
|
11
12
|
#
|
|
@@ -14,12 +15,10 @@ module Reek
|
|
|
14
15
|
#
|
|
15
16
|
class CodeComment
|
|
16
17
|
CONFIGURATION_REGEX = /
|
|
17
|
-
:reek:
|
|
18
|
-
(\w+)
|
|
19
|
-
(
|
|
20
|
-
|
|
21
|
-
(\{.*?\}) # optional details in hash style e.g.: { max_methods: 30 }
|
|
22
|
-
)?
|
|
18
|
+
:reek: # prefix
|
|
19
|
+
(\w+) # smell detector e.g.: UncommunicativeVariableName
|
|
20
|
+
(:?\s*) # separator
|
|
21
|
+
(\{.*?\})? # details in hash style e.g.: { max_methods: 30 }
|
|
23
22
|
/x.freeze
|
|
24
23
|
SANITIZE_REGEX = /(#|\n|\s)+/.freeze # Matches '#', newlines and > 1 whitespaces.
|
|
25
24
|
DISABLE_DETECTOR_CONFIGURATION = '{ enabled: false }'
|
|
@@ -38,7 +37,8 @@ module Reek
|
|
|
38
37
|
@source = source
|
|
39
38
|
@config = Hash.new { |hash, key| hash[key] = {} }
|
|
40
39
|
|
|
41
|
-
@original_comment.scan(CONFIGURATION_REGEX) do |detector_name,
|
|
40
|
+
@original_comment.scan(CONFIGURATION_REGEX) do |detector_name, separator, options|
|
|
41
|
+
escalate_legacy_separator separator
|
|
42
42
|
CodeCommentValidator.new(detector_name: detector_name,
|
|
43
43
|
original_comment: original_comment,
|
|
44
44
|
line: line,
|
|
@@ -64,6 +64,14 @@ module Reek
|
|
|
64
64
|
strip
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
def escalate_legacy_separator(separator)
|
|
68
|
+
return unless separator.start_with? ':'
|
|
69
|
+
|
|
70
|
+
raise Errors::LegacyCommentSeparatorError.new(original_comment: original_comment,
|
|
71
|
+
source: source,
|
|
72
|
+
line: line)
|
|
73
|
+
end
|
|
74
|
+
|
|
67
75
|
#
|
|
68
76
|
# A typical configuration via code comment looks like this:
|
|
69
77
|
#
|
|
@@ -88,25 +96,22 @@ module Reek
|
|
|
88
96
|
# @param source [String] path to source file or "string"
|
|
89
97
|
# @param options [String] the configuration options as String for the detector that were
|
|
90
98
|
# extracted from the original comment
|
|
91
|
-
def initialize(detector_name:, original_comment:, line:, source:, options:
|
|
99
|
+
def initialize(detector_name:, original_comment:, line:, source:, options:)
|
|
92
100
|
@detector_name = detector_name
|
|
93
101
|
@original_comment = original_comment
|
|
94
102
|
@line = line
|
|
95
103
|
@source = source
|
|
96
104
|
@options = options
|
|
97
|
-
@detector_class = nil # We only know this one after our first initial checks
|
|
98
|
-
@parsed_options = nil # We only know this one after our first initial checks
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
#
|
|
102
108
|
# Method can raise the following errors:
|
|
109
|
+
# * Errors::LegacyCommentSeparatorError
|
|
103
110
|
# * Errors::BadDetectorInCommentError
|
|
104
111
|
# * Errors::GarbageDetectorConfigurationInCommentError
|
|
105
112
|
# * Errors::BadDetectorConfigurationKeyInCommentError
|
|
106
113
|
# @return [undefined]
|
|
107
114
|
def validate
|
|
108
|
-
escalate_bad_detector
|
|
109
|
-
escalate_bad_detector_configuration
|
|
110
115
|
escalate_unknown_configuration_key
|
|
111
116
|
end
|
|
112
117
|
|
|
@@ -116,22 +121,12 @@ module Reek
|
|
|
116
121
|
:original_comment,
|
|
117
122
|
:line,
|
|
118
123
|
:source,
|
|
119
|
-
:
|
|
120
|
-
:
|
|
121
|
-
:parsed_options
|
|
122
|
-
|
|
123
|
-
def escalate_bad_detector
|
|
124
|
-
return if SmellDetectors::BaseDetector.valid_detector?(detector_name)
|
|
124
|
+
:separator,
|
|
125
|
+
:options
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
line: line)
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def escalate_bad_detector_configuration
|
|
133
|
-
@parsed_options = YAML.safe_load(options || CodeComment::DISABLE_DETECTOR_CONFIGURATION,
|
|
134
|
-
permitted_classes: [Regexp])
|
|
127
|
+
def parsed_options
|
|
128
|
+
@parsed_options ||= YAML.safe_load(options || CodeComment::DISABLE_DETECTOR_CONFIGURATION,
|
|
129
|
+
permitted_classes: [Regexp])
|
|
135
130
|
rescue Psych::SyntaxError
|
|
136
131
|
raise Errors::GarbageDetectorConfigurationInCommentError.new(detector_name: detector_name,
|
|
137
132
|
original_comment: original_comment,
|
|
@@ -140,8 +135,6 @@ module Reek
|
|
|
140
135
|
end
|
|
141
136
|
|
|
142
137
|
def escalate_unknown_configuration_key
|
|
143
|
-
@detector_class = SmellDetectors::BaseDetector.to_detector(detector_name)
|
|
144
|
-
|
|
145
138
|
return if given_keys_legit?
|
|
146
139
|
|
|
147
140
|
raise Errors::BadDetectorConfigurationKeyInCommentError.new(detector_name: detector_name,
|
|
@@ -151,6 +144,20 @@ module Reek
|
|
|
151
144
|
line: line)
|
|
152
145
|
end
|
|
153
146
|
|
|
147
|
+
def detector_class
|
|
148
|
+
@detector_class ||= SmellDetectors::BaseDetector.to_detector(detector_name)
|
|
149
|
+
rescue NameError
|
|
150
|
+
raise Errors::BadDetectorInCommentError.new(detector_name: detector_name,
|
|
151
|
+
original_comment: original_comment,
|
|
152
|
+
source: source,
|
|
153
|
+
line: line)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @return [Boolean] comment uses legacy three-colon format
|
|
157
|
+
def legacy_format?
|
|
158
|
+
separator.start_with? ':'
|
|
159
|
+
end
|
|
160
|
+
|
|
154
161
|
# @return [Boolean] all keys in code comment are applicable to the detector in question
|
|
155
162
|
def given_keys_legit?
|
|
156
163
|
given_configuration_keys.subset? valid_detector_keys
|
|
@@ -72,11 +72,12 @@ module Reek
|
|
|
72
72
|
|
|
73
73
|
def load_values(values)
|
|
74
74
|
values.each do |key, value|
|
|
75
|
-
|
|
75
|
+
case key
|
|
76
|
+
when EXCLUDE_PATHS_KEY
|
|
76
77
|
excluded_paths.add value
|
|
77
|
-
|
|
78
|
+
when DIRECTORIES_KEY
|
|
78
79
|
directory_directives.add value
|
|
79
|
-
|
|
80
|
+
when DETECTORS_KEY
|
|
80
81
|
default_directive.add value
|
|
81
82
|
end
|
|
82
83
|
end
|
|
@@ -74,7 +74,7 @@ module Reek
|
|
|
74
74
|
return unless configuration[DETECTORS_KEY]
|
|
75
75
|
|
|
76
76
|
configuration[DETECTORS_KEY].tap do |detectors|
|
|
77
|
-
detectors.
|
|
77
|
+
detectors.each_key do |detector|
|
|
78
78
|
convertible_attributes(detectors[detector]).each do |attribute|
|
|
79
79
|
detectors[detector][attribute] = detectors[detector][attribute].map do |item|
|
|
80
80
|
to_regex item
|
|
@@ -94,7 +94,7 @@ module Reek
|
|
|
94
94
|
return unless configuration[DIRECTORIES_KEY]
|
|
95
95
|
|
|
96
96
|
configuration[DIRECTORIES_KEY].tap do |directories|
|
|
97
|
-
directories.
|
|
97
|
+
directories.each_key do |directory|
|
|
98
98
|
directories[directory].each do |detector, configuration|
|
|
99
99
|
convertible_attributes(configuration).each do |attribute|
|
|
100
100
|
directories[directory][detector][attribute] = directories[directory][detector][attribute].map do |item|
|
|
@@ -53,10 +53,16 @@ module Reek
|
|
|
53
53
|
# @quality :reek:FeatureEnvy
|
|
54
54
|
def best_match_for(source_base_dir)
|
|
55
55
|
keys.
|
|
56
|
-
select
|
|
56
|
+
select do |pathname|
|
|
57
|
+
match?(source_base_dir, pathname) || match?(source_base_dir, pathname.expand_path)
|
|
58
|
+
end.
|
|
57
59
|
max_by { |pathname| pathname.to_s.length }
|
|
58
60
|
end
|
|
59
61
|
|
|
62
|
+
def match?(source_base_dir, pathname)
|
|
63
|
+
source_base_dir.to_s.match(glob_to_regexp(pathname.to_s))
|
|
64
|
+
end
|
|
65
|
+
|
|
60
66
|
# Transform a glob pattern to a regexp.
|
|
61
67
|
#
|
|
62
68
|
# It changes:
|
|
@@ -78,7 +84,7 @@ module Reek
|
|
|
78
84
|
gsub('<<to_eol_wildcards>>', '.*').
|
|
79
85
|
gsub('<<to_wildcards>>', '.*')
|
|
80
86
|
else
|
|
81
|
-
glob
|
|
87
|
+
"#{glob}.*"
|
|
82
88
|
end
|
|
83
89
|
|
|
84
90
|
Regexp.new("^#{regexp}$", Regexp::IGNORECASE)
|
|
@@ -86,7 +92,7 @@ module Reek
|
|
|
86
92
|
|
|
87
93
|
def error_message_for_invalid_smell_type(klass)
|
|
88
94
|
"You are trying to configure smell type #{klass} but we can't find one with that name.\n" \
|
|
89
|
-
"Please make sure you spelled it right. (See 'docs/defaults.reek' in the Reek\n" \
|
|
95
|
+
"Please make sure you spelled it right. (See 'docs/defaults.reek.yml' in the Reek\n" \
|
|
90
96
|
'repository for a list of all available smell types.)'
|
|
91
97
|
end
|
|
92
98
|
end
|
|
@@ -66,6 +66,8 @@ module Reek
|
|
|
66
66
|
CodeComment.new(comment: exp.leading_comment).descriptive?
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
CONSTANT_SEXP_TYPES = [:casgn, :class, :module].freeze
|
|
70
|
+
|
|
69
71
|
# A namespace module is a module (or class) that is only there for namespacing
|
|
70
72
|
# purposes, and thus contains only nested constants, modules or classes.
|
|
71
73
|
#
|
|
@@ -78,7 +80,7 @@ module Reek
|
|
|
78
80
|
return false if exp.type == :casgn
|
|
79
81
|
|
|
80
82
|
children = exp.direct_children
|
|
81
|
-
children.any? && children.all? { |child|
|
|
83
|
+
children.any? && children.all? { |child| CONSTANT_SEXP_TYPES.include? child.type }
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
def track_visibility(visibility, names)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'module_context'
|
|
4
|
+
|
|
5
|
+
module Reek
|
|
6
|
+
module Context
|
|
7
|
+
#
|
|
8
|
+
# A context wrapper for any refinement blocks found in a syntax tree.
|
|
9
|
+
#
|
|
10
|
+
class RefinementContext < ModuleContext
|
|
11
|
+
def full_name
|
|
12
|
+
exp.call.args.first.name
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/reek/context_builder.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative 'context/class_context'
|
|
|
5
5
|
require_relative 'context/ghost_context'
|
|
6
6
|
require_relative 'context/method_context'
|
|
7
7
|
require_relative 'context/module_context'
|
|
8
|
+
require_relative 'context/refinement_context'
|
|
8
9
|
require_relative 'context/root_context'
|
|
9
10
|
require_relative 'context/send_context'
|
|
10
11
|
require_relative 'context/singleton_attribute_context'
|
|
@@ -20,7 +21,7 @@ module Reek
|
|
|
20
21
|
# counting. Ideally `ContextBuilder` would only build up the context tree and leave the
|
|
21
22
|
# statement and reference counting to the contexts.
|
|
22
23
|
#
|
|
23
|
-
# @quality :reek:TooManyMethods { max_methods:
|
|
24
|
+
# @quality :reek:TooManyMethods { max_methods: 32 }
|
|
24
25
|
# @quality :reek:UnusedPrivateMethod { exclude: [ !ruby/regexp /process_/ ] }
|
|
25
26
|
# @quality :reek:DataClump
|
|
26
27
|
class ContextBuilder
|
|
@@ -263,9 +264,16 @@ module Reek
|
|
|
263
264
|
#
|
|
264
265
|
# Counts non-empty blocks as one statement.
|
|
265
266
|
#
|
|
267
|
+
# A refinement block is handled differently and causes a RefinementContext
|
|
268
|
+
# to be opened.
|
|
269
|
+
#
|
|
266
270
|
def process_block(exp, _parent)
|
|
267
271
|
increase_statement_count_by(exp.block)
|
|
268
|
-
|
|
272
|
+
if exp.call.name == :refine
|
|
273
|
+
handle_refinement_block(exp)
|
|
274
|
+
else
|
|
275
|
+
process(exp)
|
|
276
|
+
end
|
|
269
277
|
end
|
|
270
278
|
|
|
271
279
|
# Handles `begin` and `kwbegin` nodes. `begin` nodes are created implicitly
|
|
@@ -508,6 +516,12 @@ module Reek
|
|
|
508
516
|
end
|
|
509
517
|
end
|
|
510
518
|
|
|
519
|
+
def handle_refinement_block(exp)
|
|
520
|
+
inside_new_context(Context::RefinementContext, exp) do
|
|
521
|
+
process(exp)
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
511
525
|
def handle_send_for_modules(exp)
|
|
512
526
|
arg_names = exp.args.map { |arg| arg.children.first }
|
|
513
527
|
current_context.track_visibility(exp.name, arg_names)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_error'
|
|
4
|
+
|
|
5
|
+
module Reek
|
|
6
|
+
module Errors
|
|
7
|
+
# Gets raised for old-style comment configuration format.
|
|
8
|
+
class LegacyCommentSeparatorError < BaseError
|
|
9
|
+
MESSAGE = <<-MESSAGE
|
|
10
|
+
Error: You are using the legacy configuration format (including three
|
|
11
|
+
colons) to configure Reek in one your source code comments.
|
|
12
|
+
|
|
13
|
+
The source is '%<source>s' and the comment belongs to the expression
|
|
14
|
+
starting in line %<line>d.
|
|
15
|
+
|
|
16
|
+
Here's the original comment:
|
|
17
|
+
|
|
18
|
+
%<comment>s
|
|
19
|
+
|
|
20
|
+
Please see the Reek docs for information on how to configure Reek via
|
|
21
|
+
source code comments: #{DocumentationLink.build('Smell Suppression')}
|
|
22
|
+
|
|
23
|
+
Update the offensive comment and re-run Reek.
|
|
24
|
+
|
|
25
|
+
MESSAGE
|
|
26
|
+
|
|
27
|
+
def initialize(source:, line:, original_comment:)
|
|
28
|
+
message = format(MESSAGE,
|
|
29
|
+
source: source,
|
|
30
|
+
line: line,
|
|
31
|
+
comment: original_comment)
|
|
32
|
+
super message
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -59,7 +59,7 @@ BooleanParameter:
|
|
|
59
59
|
|
|
60
60
|
## Getting rid of the smell
|
|
61
61
|
|
|
62
|
-
This is highly
|
|
62
|
+
This is highly dependent on your exact architecture, but looking at the example above what you could do is:
|
|
63
63
|
|
|
64
64
|
* Move everything in the `if` branch into a separate method
|
|
65
65
|
* Move everything in the `else` branch into a separate method
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../base_report'
|
|
4
|
+
require_relative 'code_climate_formatter'
|
|
4
5
|
|
|
5
6
|
module Reek
|
|
6
7
|
module Report
|
|
@@ -12,7 +13,7 @@ module Reek
|
|
|
12
13
|
class CodeClimateReport < BaseReport
|
|
13
14
|
def show(out = $stdout)
|
|
14
15
|
smells.map do |smell|
|
|
15
|
-
out.print
|
|
16
|
+
out.print CodeClimateFormatter.new(smell).render
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
19
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'code_climate/code_climate_formatter'
|
|
4
|
-
|
|
5
3
|
module Reek
|
|
6
4
|
module Report
|
|
7
5
|
#
|
|
@@ -17,11 +15,6 @@ module Reek
|
|
|
17
15
|
"#{location_formatter.format(warning)}#{warning.base_message}"
|
|
18
16
|
end
|
|
19
17
|
|
|
20
|
-
# @quality :reek:UtilityFunction
|
|
21
|
-
def format_code_climate_hash(warning)
|
|
22
|
-
CodeClimateFormatter.new(warning).render
|
|
23
|
-
end
|
|
24
|
-
|
|
25
18
|
def format_list(warnings)
|
|
26
19
|
warnings.map { |warning| " #{format(warning)}" }.join("\n")
|
|
27
20
|
end
|
data/lib/reek/report.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'report/code_climate'
|
|
4
3
|
require_relative 'report/html_report'
|
|
5
4
|
require_relative 'report/json_report'
|
|
6
5
|
require_relative 'report/text_report'
|
|
@@ -17,12 +16,11 @@ module Reek
|
|
|
17
16
|
# Reek reporting functionality.
|
|
18
17
|
module Report
|
|
19
18
|
REPORT_CLASSES = {
|
|
20
|
-
yaml:
|
|
21
|
-
json:
|
|
22
|
-
html:
|
|
23
|
-
xml:
|
|
24
|
-
text:
|
|
25
|
-
code_climate: CodeClimateReport
|
|
19
|
+
yaml: YAMLReport,
|
|
20
|
+
json: JSONReport,
|
|
21
|
+
html: HTMLReport,
|
|
22
|
+
xml: XMLReport,
|
|
23
|
+
text: TextReport
|
|
26
24
|
}.freeze
|
|
27
25
|
|
|
28
26
|
LOCATION_FORMATTERS = {
|
|
@@ -19,6 +19,7 @@ module Reek
|
|
|
19
19
|
# @quality :reek:TooManyMethods { max_methods: 18 }
|
|
20
20
|
class BaseDetector
|
|
21
21
|
attr_reader :config
|
|
22
|
+
|
|
22
23
|
# The name of the config field that lists the names of code contexts
|
|
23
24
|
# that should not be checked. Add this field to the config for each
|
|
24
25
|
# smell that should ignore this code element.
|
|
@@ -121,15 +122,6 @@ module Reek
|
|
|
121
122
|
@descendants ||= []
|
|
122
123
|
end
|
|
123
124
|
|
|
124
|
-
#
|
|
125
|
-
# @param detector [String] the detector in question, e.g. 'DuplicateMethodCall'
|
|
126
|
-
# @return [Boolean]
|
|
127
|
-
#
|
|
128
|
-
def valid_detector?(detector)
|
|
129
|
-
descendants.map { |descendant| descendant.to_s.split('::').last }.
|
|
130
|
-
include?(detector)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
125
|
#
|
|
134
126
|
# Transform a detector name to the corresponding constant.
|
|
135
127
|
# Note that we assume a valid name - exceptions are not handled here.
|
|
@@ -14,6 +14,8 @@ module Reek
|
|
|
14
14
|
#
|
|
15
15
|
# See {file:docs/Boolean-Parameter.md} for details.
|
|
16
16
|
class BooleanParameter < BaseDetector
|
|
17
|
+
BOOLEAN_VALUES = [:true, :false].freeze
|
|
18
|
+
|
|
17
19
|
#
|
|
18
20
|
# Checks whether the given method has any Boolean parameters.
|
|
19
21
|
#
|
|
@@ -21,7 +23,7 @@ module Reek
|
|
|
21
23
|
#
|
|
22
24
|
def sniff
|
|
23
25
|
context.default_assignments.select do |_parameter, value|
|
|
24
|
-
|
|
26
|
+
BOOLEAN_VALUES.include?(value.type)
|
|
25
27
|
end.map do |parameter, _value|
|
|
26
28
|
smell_warning(
|
|
27
29
|
lines: [source_line],
|
|
@@ -51,7 +51,7 @@ module Reek
|
|
|
51
51
|
# @return [Array<SmellWarning>]
|
|
52
52
|
#
|
|
53
53
|
def sniff
|
|
54
|
-
|
|
54
|
+
clumps.map do |clump, methods|
|
|
55
55
|
methods_length = methods.length
|
|
56
56
|
smell_warning(
|
|
57
57
|
lines: methods.map(&:line),
|
|
@@ -72,72 +72,39 @@ module Reek
|
|
|
72
72
|
private
|
|
73
73
|
|
|
74
74
|
def max_copies
|
|
75
|
-
value(MAX_COPIES_KEY, context)
|
|
75
|
+
@max_copies ||= value(MAX_COPIES_KEY, context)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def min_clump_size
|
|
79
|
-
value(MIN_CLUMP_SIZE_KEY, context)
|
|
79
|
+
@min_clump_size ||= value(MIN_CLUMP_SIZE_KEY, context)
|
|
80
80
|
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class MethodGroup
|
|
87
|
-
def initialize(ctx, min_clump_size, max_copies)
|
|
88
|
-
@min_clump_size = min_clump_size
|
|
89
|
-
@max_copies = max_copies
|
|
90
|
-
@candidate_methods = ctx.node_instance_methods.map do |defn_node|
|
|
91
|
-
CandidateMethod.new(defn_node)
|
|
82
|
+
def candidate_methods
|
|
83
|
+
@candidate_methods ||= context.node_instance_methods
|
|
92
84
|
end
|
|
93
|
-
end
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# @quality :reek:UtilityFunction
|
|
104
|
-
def common_argument_names_for(methods)
|
|
105
|
-
methods.map(&:arg_names).inject(:&)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def methods_containing_clump(clump)
|
|
109
|
-
candidate_methods.select { |method| clump & method.arg_names == clump }
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def clumps
|
|
113
|
-
candidate_clumps.map do |clump|
|
|
114
|
-
[clump, methods_containing_clump(clump)]
|
|
86
|
+
def candidate_clumps
|
|
87
|
+
candidate_methods.each_cons(max_copies + 1).map do |methods|
|
|
88
|
+
common_argument_names_for(methods)
|
|
89
|
+
end.select do |clump|
|
|
90
|
+
clump.length >= min_clump_size
|
|
91
|
+
end.uniq
|
|
115
92
|
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
private
|
|
119
93
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# @private
|
|
125
|
-
class CandidateMethod
|
|
126
|
-
extend Forwardable
|
|
127
|
-
|
|
128
|
-
def_delegators :defn, :line, :name
|
|
94
|
+
# @quality :reek:UtilityFunction
|
|
95
|
+
def common_argument_names_for(methods)
|
|
96
|
+
methods.map(&:arg_names).inject(:&).compact.sort
|
|
97
|
+
end
|
|
129
98
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
99
|
+
def methods_containing_clump(clump)
|
|
100
|
+
candidate_methods.select { |method| clump & method.arg_names == clump }
|
|
101
|
+
end
|
|
133
102
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
103
|
+
def clumps
|
|
104
|
+
candidate_clumps.map do |clump|
|
|
105
|
+
[clump, methods_containing_clump(clump)]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
137
108
|
end
|
|
138
|
-
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
attr_reader :defn
|
|
142
109
|
end
|
|
143
110
|
end
|
|
@@ -24,8 +24,7 @@ module Reek
|
|
|
24
24
|
|
|
25
25
|
def detect_nodes
|
|
26
26
|
finders = [NodeFinder.new(context, :send, NilCallNodeDetector),
|
|
27
|
-
NodeFinder.new(context, :when, NilWhenNodeDetector)
|
|
28
|
-
NodeFinder.new(context, :csend, SafeNavigationNodeDetector)]
|
|
27
|
+
NodeFinder.new(context, :when, NilWhenNodeDetector)]
|
|
29
28
|
finders.flat_map(&:smelly_nodes)
|
|
30
29
|
end
|
|
31
30
|
|
|
@@ -88,16 +87,6 @@ module Reek
|
|
|
88
87
|
node.condition_list.any? { |it| it.type == :nil }
|
|
89
88
|
end
|
|
90
89
|
end
|
|
91
|
-
|
|
92
|
-
# Detect safe navigation. Returns true for all nodes, since all :csend
|
|
93
|
-
# nodes are considered smelly.
|
|
94
|
-
module SafeNavigationNodeDetector
|
|
95
|
-
module_function
|
|
96
|
-
|
|
97
|
-
def detect(_node)
|
|
98
|
-
true
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
90
|
end
|
|
102
91
|
end
|
|
103
92
|
end
|
data/lib/reek/smell_warning.rb
CHANGED
|
@@ -31,8 +31,7 @@ module Reek
|
|
|
31
31
|
# public API.
|
|
32
32
|
#
|
|
33
33
|
# @quality :reek:LongParameterList { max_params: 6 }
|
|
34
|
-
def initialize(smell_type, context: '',
|
|
35
|
-
source:, parameters: {})
|
|
34
|
+
def initialize(smell_type, lines:, message:, source:, context: '', parameters: {})
|
|
36
35
|
@smell_type = smell_type
|
|
37
36
|
@source = source
|
|
38
37
|
@context = context.to_s
|