reek 5.4.1 → 6.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|