reek 5.4.1 → 6.0.2
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/.gitignore +1 -0
- data/.rubocop.yml +8 -6
- data/.rubocop_todo.yml +25 -20
- data/.simplecov +1 -0
- data/.travis.yml +17 -11
- data/CHANGELOG.md +31 -3
- data/Dockerfile +1 -0
- data/Gemfile +14 -17
- data/README.md +15 -11
- data/bin/code_climate_reek +12 -2
- data/docs/Attribute.md +1 -1
- data/docs/Boolean-Parameter.md +1 -1
- data/docs/Control-Couple.md +1 -1
- data/docs/Nil-Check.md +4 -1
- 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 +1 -2
- data/lib/reek.rb +1 -0
- data/lib/reek/ast/sexp_extensions/arguments.rb +11 -0
- data/lib/reek/cli/options.rb +3 -3
- data/lib/reek/code_comment.rb +45 -38
- 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/context/module_context.rb +3 -1
- data/lib/reek/errors/legacy_comment_separator_error.rb +36 -0
- data/lib/reek/examiner.rb +3 -3
- data/lib/reek/report.rb +5 -7
- 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/text_report.rb +2 -2
- 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/subclassed_from_core_class.rb +3 -7
- data/lib/reek/smell_detectors/uncommunicative_variable_name.rb +1 -1
- data/lib/reek/smell_warning.rb +1 -2
- data/lib/reek/source/source_code.rb +3 -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/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/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/code_climate/code_climate_report_spec.rb +1 -1
- data/spec/reek/report/json_report_spec.rb +1 -1
- data/spec/reek/report/location_formatter_spec.rb +3 -3
- data/spec/reek/report/text_report_spec.rb +1 -7
- data/spec/reek/report/yaml_report_spec.rb +1 -1
- 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_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 +20 -6
- data/tasks/configuration.rake +1 -2
- metadata +15 -25
- data/spec/factories/factories.rb +0 -37
data/lib/reek/examiner.rb
CHANGED
@@ -105,11 +105,11 @@ module Reek
|
|
105
105
|
rescue Errors::BaseError
|
106
106
|
raise
|
107
107
|
rescue EncodingError
|
108
|
-
raise Errors::EncodingError
|
108
|
+
raise Errors::EncodingError.new(origin: origin)
|
109
109
|
rescue Parser::SyntaxError
|
110
|
-
raise Errors::SyntaxError
|
110
|
+
raise Errors::SyntaxError.new(origin: origin)
|
111
111
|
rescue StandardError
|
112
|
-
raise Errors::IncomprehensibleSourceError
|
112
|
+
raise Errors::IncomprehensibleSourceError.new(origin: origin)
|
113
113
|
end
|
114
114
|
|
115
115
|
def syntax_tree
|
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 = {
|
@@ -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
|
@@ -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
|
@@ -43,13 +43,9 @@ module Reek
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def build_smell_warning(ancestor_name)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
parameters: { ancestor: ancestor_name }
|
50
|
-
}
|
51
|
-
|
52
|
-
smell_warning(smell_attributes)
|
46
|
+
smell_warning(lines: [source_line],
|
47
|
+
message: "inherits from core class '#{ancestor_name}'",
|
48
|
+
parameters: { ancestor: ancestor_name })
|
53
49
|
end
|
54
50
|
end
|
55
51
|
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
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../cli/silencer'
|
4
|
-
|
4
|
+
# Silence Parser's warnings about Ruby micro version differences
|
5
|
+
Reek::CLI::Silencer.silently { require 'parser/current' }
|
5
6
|
require_relative '../tree_dresser'
|
6
7
|
require_relative '../ast/node'
|
7
8
|
require_relative '../ast/builder'
|
@@ -53,7 +54,7 @@ module Reek
|
|
53
54
|
end
|
54
55
|
|
55
56
|
def self.default_parser
|
56
|
-
Parser::
|
57
|
+
Parser::CurrentRuby.new(AST::Builder.new).tap do |parser|
|
57
58
|
diagnostics = parser.diagnostics
|
58
59
|
diagnostics.all_errors_are_fatal = true
|
59
60
|
diagnostics.ignore_warnings = true
|
@@ -33,23 +33,26 @@ module Reek
|
|
33
33
|
|
34
34
|
attr_reader :configuration, :paths, :options
|
35
35
|
|
36
|
-
# @quality :reek:TooManyStatements { max_statements: 7 }
|
37
|
-
# @quality :reek:NestedIterators { max_allowed_nesting: 2 }
|
38
36
|
def source_paths
|
39
37
|
paths.each_with_object([]) do |given_path, relevant_paths|
|
40
|
-
|
38
|
+
if given_path.exist?
|
39
|
+
relevant_paths.concat source_files_from_path(given_path)
|
40
|
+
else
|
41
41
|
print_no_such_file_error(given_path)
|
42
|
-
next
|
43
42
|
end
|
43
|
+
end
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
def source_files_from_path(given_path)
|
47
|
+
relevant_paths = []
|
48
|
+
given_path.find do |path|
|
49
|
+
if path.directory?
|
50
|
+
Find.prune if ignore_path?(path)
|
51
|
+
elsif ruby_file?(path)
|
52
|
+
relevant_paths << path unless ignore_file?(path)
|
51
53
|
end
|
52
54
|
end
|
55
|
+
relevant_paths
|
53
56
|
end
|
54
57
|
|
55
58
|
def ignore_file?(path)
|
@@ -43,8 +43,9 @@ module Reek
|
|
43
43
|
raise ArgumentError, "The attribute '#{extra_keys.first}' is not available for comparison"
|
44
44
|
end
|
45
45
|
|
46
|
+
# :reek:FeatureEnvy
|
46
47
|
def common_parameters_equal?(other_parameters)
|
47
|
-
smell_warning.parameters.
|
48
|
+
smell_warning.parameters.values_at(*other_parameters.keys) == other_parameters.values
|
48
49
|
end
|
49
50
|
|
50
51
|
def common_attributes_equal?(attributes)
|
data/lib/reek/version.rb
CHANGED
data/reek.gemspec
CHANGED
@@ -16,12 +16,19 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.executables = s.files.grep(%r{^bin/}).map { |path| File.basename(path) }
|
17
17
|
s.homepage = 'https://github.com/troessner/reek'
|
18
18
|
s.rdoc_options = %w(--main README.md -x assets/|bin/|config/|features/|spec/|tasks/)
|
19
|
-
s.required_ruby_version = '>= 2.
|
19
|
+
s.required_ruby_version = '>= 2.4.0'
|
20
20
|
s.summary = 'Code smell detector for Ruby'
|
21
21
|
|
22
|
-
s.
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
22
|
+
s.metadata = {
|
23
|
+
'homepage_uri' => 'https://github.com/troessner/reek',
|
24
|
+
'source_code_uri' => 'https://github.com/troessner/reek',
|
25
|
+
'bug_tracker_uri' => 'https://github.com/troessner/reek/issues',
|
26
|
+
'changelog_uri' => 'https://github.com/troessner/reek/CHANGELOG.md',
|
27
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/reek'
|
28
|
+
}
|
29
|
+
|
30
|
+
s.add_runtime_dependency 'kwalify', '~> 0.7.0'
|
31
|
+
s.add_runtime_dependency 'parser', '< 2.8', '>= 2.5.0.0', '!= 2.5.1.1'
|
32
|
+
s.add_runtime_dependency 'psych', '~> 3.1'
|
33
|
+
s.add_runtime_dependency 'rainbow', '>= 2.0', '< 4.0'
|
27
34
|
end
|
@@ -6,10 +6,8 @@ RSpec.describe 'Runtime speed' do
|
|
6
6
|
|
7
7
|
it 'runs on our smelly sources in less than 5 seconds' do
|
8
8
|
expect do
|
9
|
-
source_directory.
|
10
|
-
|
11
|
-
|
12
|
-
examiner = Reek::Examiner.new entry.to_path
|
9
|
+
Dir[source_directory.join('**/*.rb')].each do |entry|
|
10
|
+
examiner = Reek::Examiner.new Pathname.new(entry)
|
13
11
|
examiner.smells.size
|
14
12
|
end
|
15
13
|
end.to perform_under(5).sec
|
@@ -4,6 +4,7 @@ require 'kramdown'
|
|
4
4
|
RSpec.describe 'Documentation' do
|
5
5
|
root = File.expand_path('../..', __dir__)
|
6
6
|
files = Dir.glob(File.join(root, '*.md')) + Dir.glob(File.join(root, 'docs', '*.md'))
|
7
|
+
code_types = [:codeblock, :codespan]
|
7
8
|
|
8
9
|
files.each do |file|
|
9
10
|
describe "from #{file}" do
|
@@ -13,7 +14,7 @@ RSpec.describe 'Documentation' do
|
|
13
14
|
|
14
15
|
blocks.each do |block|
|
15
16
|
# Only consider code blocks with language 'ruby'.
|
16
|
-
next unless
|
17
|
+
next unless code_types.include?(block.type)
|
17
18
|
next unless block.attr['class'] == 'language-ruby'
|
18
19
|
|
19
20
|
it "has a valid sample at #{block.options[:location] + 1}" do
|