reek 4.5.6 → 4.6.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 +0 -13
- data/CHANGELOG.md +5 -0
- data/Gemfile +2 -2
- data/README.md +5 -2
- data/features/command_line_interface/options.feature +2 -0
- data/features/command_line_interface/stdin.feature +1 -1
- data/features/configuration_files/exclude_paths_directives.feature +19 -0
- data/features/todo_list.feature +1 -2
- data/lib/reek/ast/sexp_extensions/arguments.rb +0 -5
- data/lib/reek/ast/sexp_extensions/constant.rb +1 -0
- data/lib/reek/cli/application.rb +2 -4
- data/lib/reek/cli/options.rb +19 -4
- data/lib/reek/errors/parse_error.rb +19 -0
- data/lib/reek/source/source_code.rb +2 -1
- data/lib/reek/source/source_locator.rb +15 -3
- data/lib/reek/spec.rb +2 -0
- data/lib/reek/version.rb +1 -1
- data/spec/factories/factories.rb +0 -8
- data/spec/reek/cli/command/todo_list_command_spec.rb +12 -69
- data/spec/reek/cli/options_spec.rb +4 -0
- data/spec/reek/context/code_context_spec.rb +10 -10
- data/spec/reek/context/method_context_spec.rb +1 -1
- data/spec/reek/context/module_context_spec.rb +8 -4
- data/spec/reek/examiner_spec.rb +4 -4
- data/spec/reek/report/code_climate/code_climate_fingerprint_spec.rb +9 -9
- data/spec/reek/smell_warning_spec.rb +6 -6
- data/spec/reek/source/source_code_spec.rb +6 -29
- data/spec/reek/source/source_locator_spec.rb +48 -16
- data/spec/reek/spec/should_reek_only_of_spec.rb +4 -4
- data/spec/spec_helper.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c827e3718e3a4f1524fdaf0d6c28df6e86b671b
|
4
|
+
data.tar.gz: 9755661abe970fb1499ee2b9de2f6d701b8c8ee2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77122032bb96bbd32994195c9745acc214ecfbfff7963348aff01239402deefb74032883eed3762df8eda34c6054447a5ca076dfe707a9a1460a5c14bc85ddf7
|
7
|
+
data.tar.gz: e86a819d479502f6f091c6d6f8b7d1b6c1aae20779131a3e6e62445eb048db3f1f0e1226b59a0b11ca82e9f3c4471578ce4c71c19d0a1fdff96b51f8f5e7d752
|
data/.rubocop.yml
CHANGED
@@ -49,12 +49,6 @@ RSpec/DescribeClass:
|
|
49
49
|
RSpec/ExampleLength:
|
50
50
|
Enabled: false
|
51
51
|
|
52
|
-
# FIXME: Find a better way to block output during specs, and fix relevant examples.
|
53
|
-
RSpec/ExpectOutput:
|
54
|
-
Exclude:
|
55
|
-
- 'spec/reek/cli/command/todo_list_command_spec.rb'
|
56
|
-
- 'spec/reek/source/source_code_spec.rb'
|
57
|
-
|
58
52
|
# FIXME: Split up files to avoid offenses
|
59
53
|
RSpec/MultipleDescribes:
|
60
54
|
Exclude:
|
@@ -79,13 +73,6 @@ RSpec/NestedGroups:
|
|
79
73
|
Exclude:
|
80
74
|
- 'spec/reek/cli/application_spec.rb'
|
81
75
|
|
82
|
-
# FIXME: Update specs to avoid offenses
|
83
|
-
RSpec/VerifiedDoubles:
|
84
|
-
Exclude:
|
85
|
-
- 'spec/reek/context/code_context_spec.rb'
|
86
|
-
- 'spec/reek/context/method_context_spec.rb'
|
87
|
-
- 'spec/reek/context/module_context_spec.rb'
|
88
|
-
|
89
76
|
# rubocop-rspec expects a CodeClimate namespace to go with the code_climate directory.
|
90
77
|
RSpec/FilePath:
|
91
78
|
Exclude:
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -12,12 +12,12 @@ group :development do
|
|
12
12
|
gem 'factory_girl', '~> 4.0'
|
13
13
|
gem 'rake', '~> 12.0'
|
14
14
|
gem 'rspec', '~> 3.0'
|
15
|
-
gem 'simplecov', '~> 0.
|
15
|
+
gem 'simplecov', '~> 0.14.0'
|
16
16
|
gem 'yard', '~> 0.9.5'
|
17
17
|
|
18
18
|
if RUBY_VERSION >= '2.3'
|
19
19
|
gem 'rubocop', '~> 0.47.1'
|
20
|
-
gem 'rubocop-rspec', '~> 1.
|
20
|
+
gem 'rubocop-rspec', '~> 1.13.0'
|
21
21
|
end
|
22
22
|
|
23
23
|
platforms :mri do
|
data/README.md
CHANGED
@@ -475,7 +475,7 @@ Or just run the whole test suite:
|
|
475
475
|
bundle exec rake
|
476
476
|
```
|
477
477
|
|
478
|
-
This will run the
|
478
|
+
This will run the RSpec tests, RuboCop and Reek itself.
|
479
479
|
|
480
480
|
You can also run:
|
481
481
|
|
@@ -484,7 +484,7 @@ bundle exec rake ci
|
|
484
484
|
```
|
485
485
|
|
486
486
|
This will run everything the default task runs and also
|
487
|
-
[Ataru](https://github.com/CodePadawans/ataru). This is the task that we run on
|
487
|
+
our Cucumber features and [Ataru](https://github.com/CodePadawans/ataru). This is the task that we run on
|
488
488
|
Travis as well and that determines if your pull request is green or red.
|
489
489
|
|
490
490
|
Another useful Rake task is the `console` task. This will throw you right into an environment where you can play around with Reeks modules and classes:
|
@@ -557,6 +557,9 @@ Just add this to your configuration file:
|
|
557
557
|
enabled: false
|
558
558
|
UtilityFunction:
|
559
559
|
enabled: false
|
560
|
+
"app/mailers":
|
561
|
+
InstanceVariableAssumption:
|
562
|
+
enabled: false
|
560
563
|
```
|
561
564
|
|
562
565
|
Be careful though, Reek does not merge your configuration entries, so if you already have a directory directive for "app/controllers" or "app/helpers" you need to update those directives instead of copying the above YAML sample into your configuration file.
|
@@ -67,6 +67,8 @@ Feature: Reek can be controlled using command-line options
|
|
67
67
|
--sort-by SORTING Sort reported files by the given criterium:
|
68
68
|
smelliness ("smelliest" files first)
|
69
69
|
none (default - output in processing order)
|
70
|
+
--force-exclusion Force excluding files specified in the configuration `exclude_paths`
|
71
|
+
even if they are explicitly passed as arguments
|
70
72
|
|
71
73
|
Exit codes:
|
72
74
|
--success-exit-code CODE The exit code when no smells are found (default: 0)
|
@@ -35,5 +35,5 @@ Feature: Reek reads from $stdin when no files are given
|
|
35
35
|
Scenario: syntax error causes the source to be ignored
|
36
36
|
When I pass "= invalid syntax =" to reek
|
37
37
|
Then it reports a parsing error
|
38
|
-
|
38
|
+
And the exit status indicates an error
|
39
39
|
And it reports nothing
|
@@ -22,3 +22,22 @@ Feature: Exclude paths directives
|
|
22
22
|
When I run `reek -c config.reek .`
|
23
23
|
Then it succeeds
|
24
24
|
And it reports nothing
|
25
|
+
Scenario: Using a file name within an excluded directory
|
26
|
+
Given a file named "bad_files_live_here/smelly.rb" with:
|
27
|
+
"""
|
28
|
+
# A smelly example class
|
29
|
+
class Smelly
|
30
|
+
def alfa(bravo); end
|
31
|
+
end
|
32
|
+
"""
|
33
|
+
And a file named "config.reek" with:
|
34
|
+
"""
|
35
|
+
---
|
36
|
+
exclude_paths:
|
37
|
+
- bad_files_live_here
|
38
|
+
"""
|
39
|
+
When I run `reek -c config.reek bad_files_live_here/smelly.rb`
|
40
|
+
Then the exit status indicates smells
|
41
|
+
When I run `reek -c config.reek --force-exclusion bad_files_live_here/smelly.rb`
|
42
|
+
Then it succeeds
|
43
|
+
And it reports nothing
|
data/features/todo_list.feature
CHANGED
data/lib/reek/cli/application.rb
CHANGED
@@ -18,7 +18,6 @@ module Reek
|
|
18
18
|
|
19
19
|
def initialize(argv)
|
20
20
|
@options = configure_options(argv)
|
21
|
-
@status = options.success_exit_code
|
22
21
|
@configuration = configure_app_configuration(options.config_file)
|
23
22
|
@command = command_class.new(options: options,
|
24
23
|
sources: sources,
|
@@ -31,7 +30,6 @@ module Reek
|
|
31
30
|
|
32
31
|
private
|
33
32
|
|
34
|
-
attr_accessor :status
|
35
33
|
attr_reader :command, :options
|
36
34
|
|
37
35
|
def configure_options(argv)
|
@@ -81,11 +79,11 @@ module Reek
|
|
81
79
|
end
|
82
80
|
|
83
81
|
def working_directory_as_source
|
84
|
-
Source::SourceLocator.new(['.'], configuration: configuration).sources
|
82
|
+
Source::SourceLocator.new(['.'], configuration: configuration, options: options).sources
|
85
83
|
end
|
86
84
|
|
87
85
|
def sources_from_argv
|
88
|
-
Source::SourceLocator.new(argv, configuration: configuration).sources
|
86
|
+
Source::SourceLocator.new(argv, configuration: configuration, options: options).sources
|
89
87
|
end
|
90
88
|
|
91
89
|
def source_from_pipe
|
data/lib/reek/cli/options.rb
CHANGED
@@ -11,8 +11,8 @@ module Reek
|
|
11
11
|
#
|
12
12
|
# See {file:docs/Command-Line-Options.md} for details.
|
13
13
|
#
|
14
|
-
# :reek:TooManyInstanceVariables: { max_instance_variables:
|
15
|
-
# :reek:TooManyMethods: { max_methods:
|
14
|
+
# :reek:TooManyInstanceVariables: { max_instance_variables: 12 }
|
15
|
+
# :reek:TooManyMethods: { max_methods: 18 }
|
16
16
|
# :reek:Attribute: { enabled: false }
|
17
17
|
#
|
18
18
|
class Options
|
@@ -27,7 +27,8 @@ module Reek
|
|
27
27
|
:sorting,
|
28
28
|
:success_exit_code,
|
29
29
|
:failure_exit_code,
|
30
|
-
:generate_todo_list
|
30
|
+
:generate_todo_list,
|
31
|
+
:force_exclusion
|
31
32
|
|
32
33
|
def initialize(argv = [])
|
33
34
|
@argv = argv
|
@@ -41,6 +42,7 @@ module Reek
|
|
41
42
|
@success_exit_code = Status::DEFAULT_SUCCESS_EXIT_CODE
|
42
43
|
@failure_exit_code = Status::DEFAULT_FAILURE_EXIT_CODE
|
43
44
|
@generate_todo_list = false
|
45
|
+
@force_exclusion = false
|
44
46
|
|
45
47
|
set_up_parser
|
46
48
|
end
|
@@ -51,6 +53,10 @@ module Reek
|
|
51
53
|
self
|
52
54
|
end
|
53
55
|
|
56
|
+
def force_exclusion?
|
57
|
+
@force_exclusion
|
58
|
+
end
|
59
|
+
|
54
60
|
private
|
55
61
|
|
56
62
|
# TTY output generally means the output will not undergo further
|
@@ -121,7 +127,7 @@ module Reek
|
|
121
127
|
end
|
122
128
|
end
|
123
129
|
|
124
|
-
# :reek:TooManyStatements: { max_statements:
|
130
|
+
# :reek:TooManyStatements: { max_statements: 7 }
|
125
131
|
def set_report_formatting_options
|
126
132
|
parser.separator "\nText format options:"
|
127
133
|
set_up_color_option
|
@@ -129,6 +135,7 @@ module Reek
|
|
129
135
|
set_up_location_formatting_options
|
130
136
|
set_up_progress_formatting_options
|
131
137
|
set_up_sorting_option
|
138
|
+
set_up_force_exclusion_option
|
132
139
|
end
|
133
140
|
|
134
141
|
def set_up_color_option
|
@@ -175,6 +182,14 @@ module Reek
|
|
175
182
|
end
|
176
183
|
end
|
177
184
|
|
185
|
+
def set_up_force_exclusion_option
|
186
|
+
parser.on('--force-exclusion',
|
187
|
+
'Force excluding files specified in the configuration `exclude_paths`',
|
188
|
+
' even if they are explicitly passed as arguments') do |force_exclusion|
|
189
|
+
self.force_exclusion = force_exclusion
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
178
193
|
# :reek:DuplicateMethodCall: { max_calls: 2 }
|
179
194
|
def set_exit_codes
|
180
195
|
parser.separator "\nExit codes:"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'base_error'
|
3
|
+
|
4
|
+
module Reek
|
5
|
+
module Errors
|
6
|
+
# Gets raised when Reek is unable to process the source
|
7
|
+
class ParseError < BaseError
|
8
|
+
MESSAGE_TEMPLATE = '%s: %s: %s'.freeze
|
9
|
+
|
10
|
+
def initialize(origin:, original_exception:)
|
11
|
+
message = format(MESSAGE_TEMPLATE,
|
12
|
+
origin,
|
13
|
+
original_exception.class.name,
|
14
|
+
original_exception.message)
|
15
|
+
super message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -5,6 +5,7 @@ Reek::CLI::Silencer.silently do
|
|
5
5
|
end
|
6
6
|
require_relative '../tree_dresser'
|
7
7
|
require_relative '../ast/node'
|
8
|
+
require_relative '../errors/parse_error'
|
8
9
|
|
9
10
|
# Opt in to new way of representing lambdas
|
10
11
|
Parser::Builders::Default.emit_lambda = true
|
@@ -87,7 +88,7 @@ module Reek
|
|
87
88
|
begin
|
88
89
|
ast, comments = parser.parse_with_comments(source, origin)
|
89
90
|
rescue Racc::ParseError, Parser::SyntaxError => error
|
90
|
-
|
91
|
+
raise Errors::ParseError, origin: origin, original_exception: error
|
91
92
|
end
|
92
93
|
|
93
94
|
# See https://whitequark.github.io/parser/Parser/Source/Comment/Associator.html
|
@@ -11,7 +11,8 @@ module Reek
|
|
11
11
|
# Initialize with the paths we want to search.
|
12
12
|
#
|
13
13
|
# paths - a list of paths as Strings
|
14
|
-
def initialize(paths, configuration: Configuration::AppConfiguration.default)
|
14
|
+
def initialize(paths, configuration: Configuration::AppConfiguration.default, options: Reek::CLI::Options.new)
|
15
|
+
@options = options
|
15
16
|
@paths = paths.flat_map do |string|
|
16
17
|
path = Pathname.new(string)
|
17
18
|
current_directory?(path) ? path.entries : path
|
@@ -29,7 +30,7 @@ module Reek
|
|
29
30
|
|
30
31
|
private
|
31
32
|
|
32
|
-
attr_reader :configuration, :paths
|
33
|
+
attr_reader :configuration, :paths, :options
|
33
34
|
|
34
35
|
# :reek:TooManyStatements: { max_statements: 7 }
|
35
36
|
# :reek:NestedIterators: { max_allowed_nesting: 2 }
|
@@ -44,12 +45,23 @@ module Reek
|
|
44
45
|
if path.directory?
|
45
46
|
ignore_path?(path) ? Find.prune : next
|
46
47
|
elsif ruby_file?(path)
|
47
|
-
relevant_paths << path
|
48
|
+
relevant_paths << path unless ignore_file?(path)
|
48
49
|
end
|
49
50
|
end
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
54
|
+
def ignore_file?(path)
|
55
|
+
if options.force_exclusion?
|
56
|
+
path.ascend do |ascendant|
|
57
|
+
break true if path_excluded?(ascendant)
|
58
|
+
false
|
59
|
+
end
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
53
65
|
def path_excluded?(path)
|
54
66
|
configuration.path_excluded?(path)
|
55
67
|
end
|
data/lib/reek/spec.rb
CHANGED
@@ -98,6 +98,8 @@ module Reek
|
|
98
98
|
# "reek_only_of" will fail in that case.
|
99
99
|
# 2.) "reek_only_of" doesn't support the additional smell_details hash.
|
100
100
|
#
|
101
|
+
# @param smell_type [Symbol, String, Class] The "smell type" to check for.
|
102
|
+
#
|
101
103
|
# @public
|
102
104
|
#
|
103
105
|
# :reek:UtilityFunction
|
data/lib/reek/version.rb
CHANGED
data/spec/factories/factories.rb
CHANGED
@@ -4,14 +4,6 @@ require_relative '../../lib/reek/smell_warning'
|
|
4
4
|
require_relative '../../lib/reek/cli/options'
|
5
5
|
|
6
6
|
FactoryGirl.define do
|
7
|
-
factory :context, class: Reek::Context::CodeContext do
|
8
|
-
skip_create
|
9
|
-
|
10
|
-
initialize_with do
|
11
|
-
new(nil, nil)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
7
|
factory :method_context, class: Reek::Context::MethodContext do
|
16
8
|
skip_create
|
17
9
|
transient do
|
@@ -4,34 +4,25 @@ require_lib 'reek/cli/options'
|
|
4
4
|
|
5
5
|
RSpec.describe Reek::CLI::Command::TodoListCommand do
|
6
6
|
let(:nil_check) { build :smell_detector, smell_type: :NilCheck }
|
7
|
-
let(:feature_envy) { build :smell_detector, smell_type: :FeatureEnvy }
|
8
7
|
let(:nested_iterators) { build :smell_detector, smell_type: :NestedIterators }
|
9
|
-
let(:too_many_statements) { build :smell_detector, smell_type: :TooManyStatements }
|
10
8
|
|
11
9
|
describe '#execute' do
|
12
10
|
let(:options) { Reek::CLI::Options.new [] }
|
13
11
|
let(:configuration) { instance_double 'Reek::Configuration::AppConfiguration' }
|
12
|
+
let(:sources) { [source_file] }
|
14
13
|
|
15
14
|
let(:command) do
|
16
15
|
described_class.new(options: options,
|
17
|
-
sources:
|
16
|
+
sources: sources,
|
18
17
|
configuration: configuration)
|
19
18
|
end
|
20
19
|
|
21
20
|
before do
|
22
|
-
|
23
|
-
allow(File).to receive(:write)
|
24
|
-
end
|
25
|
-
|
26
|
-
after(:all) do
|
27
|
-
$stdout = STDOUT
|
21
|
+
allow(File).to receive(:write).with(described_class::FILE_NAME, String)
|
28
22
|
end
|
29
23
|
|
30
24
|
context 'smells found' do
|
31
|
-
|
32
|
-
smells = [build(:smell_warning, context: 'Foo#bar')]
|
33
|
-
allow(command).to receive(:scan_for_smells).and_return(smells)
|
34
|
-
end
|
25
|
+
let(:source_file) { SMELLY_FILE }
|
35
26
|
|
36
27
|
it 'shows a proper message' do
|
37
28
|
expected = "\n'.todo.reek' generated! You can now use this as a starting point for your configuration.\n"
|
@@ -39,70 +30,22 @@ RSpec.describe Reek::CLI::Command::TodoListCommand do
|
|
39
30
|
end
|
40
31
|
|
41
32
|
it 'returns a success code' do
|
42
|
-
result = command.execute
|
33
|
+
result = Reek::CLI::Silencer.silently { command.execute }
|
43
34
|
expect(result).to eq(Reek::CLI::Status::DEFAULT_SUCCESS_EXIT_CODE)
|
44
35
|
end
|
45
36
|
|
46
|
-
it 'writes a todo file' do
|
47
|
-
command.execute
|
48
|
-
expected_yaml = { 'FeatureEnvy' => { 'exclude' => ['Foo#bar'] } }.to_yaml
|
49
|
-
expect(File).to have_received(:write).with(described_class::FILE_NAME, expected_yaml)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
context 'smells with duplicate context found' do
|
54
|
-
before do
|
55
|
-
smells = [
|
56
|
-
build(:smell_warning, context: 'Foo#bar', smell_detector: feature_envy),
|
57
|
-
build(:smell_warning, context: 'Foo#bar', smell_detector: feature_envy)
|
58
|
-
]
|
59
|
-
allow(command).to receive(:scan_for_smells).and_return(smells)
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'writes the context into the todo file once' do
|
63
|
-
command.execute
|
64
|
-
expected_yaml = { 'FeatureEnvy' => { 'exclude' => ['Foo#bar'] } }.to_yaml
|
65
|
-
expect(File).to have_received(:write).with(described_class::FILE_NAME, expected_yaml)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
context 'smells with default exclusions found' do
|
70
|
-
let(:smell) { build :smell_warning, smell_detector: too_many_statements, context: 'Foo#bar' }
|
71
|
-
|
72
|
-
before do
|
73
|
-
allow(command).to receive(:scan_for_smells).and_return [smell]
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'includes the default exclusions in the generated yaml' do
|
77
|
-
command.execute
|
78
|
-
expected_yaml = { 'TooManyStatements' => { 'exclude' => ['initialize', 'Foo#bar'] } }.to_yaml
|
79
|
-
expect(File).to have_received(:write).with(described_class::FILE_NAME, expected_yaml)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
context 'smells of different types found' do
|
84
|
-
before do
|
85
|
-
smells = [
|
86
|
-
build(:smell_warning, context: 'Foo#bar', smell_detector: nil_check),
|
87
|
-
build(:smell_warning, context: 'Bar#baz', smell_detector: nested_iterators)
|
88
|
-
]
|
89
|
-
allow(command).to receive(:scan_for_smells).and_return(smells)
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'writes the context into the todo file once' do
|
93
|
-
command.execute
|
37
|
+
it 'writes a todo file with exclusions for each smell' do
|
38
|
+
Reek::CLI::Silencer.silently { command.execute }
|
94
39
|
expected_yaml = {
|
95
|
-
'
|
96
|
-
'
|
40
|
+
'UncommunicativeMethodName' => { 'exclude' => ['Smelly#x'] },
|
41
|
+
'UncommunicativeVariableName' => { 'exclude' => ['Smelly#x'] }
|
97
42
|
}.to_yaml
|
98
43
|
expect(File).to have_received(:write).with(described_class::FILE_NAME, expected_yaml)
|
99
44
|
end
|
100
45
|
end
|
101
46
|
|
102
47
|
context 'no smells found' do
|
103
|
-
|
104
|
-
allow(command).to receive(:scan_for_smells).and_return []
|
105
|
-
end
|
48
|
+
let(:source_file) { CLEAN_FILE }
|
106
49
|
|
107
50
|
it 'shows a proper message' do
|
108
51
|
expected = "\n'.todo.reek' not generated because there were no smells found!\n"
|
@@ -110,12 +53,12 @@ RSpec.describe Reek::CLI::Command::TodoListCommand do
|
|
110
53
|
end
|
111
54
|
|
112
55
|
it 'returns a success code' do
|
113
|
-
result = command.execute
|
56
|
+
result = Reek::CLI::Silencer.silently { command.execute }
|
114
57
|
expect(result).to eq Reek::CLI::Status::DEFAULT_SUCCESS_EXIT_CODE
|
115
58
|
end
|
116
59
|
|
117
60
|
it 'does not write a todo file' do
|
118
|
-
command.execute
|
61
|
+
Reek::CLI::Silencer.silently { command.execute }
|
119
62
|
expect(File).not_to have_received(:write)
|
120
63
|
end
|
121
64
|
end
|
@@ -32,6 +32,10 @@ RSpec.describe Reek::CLI::Options do
|
|
32
32
|
allow($stdout).to receive_messages(tty?: false)
|
33
33
|
expect(options.progress_format).to eq :quiet
|
34
34
|
end
|
35
|
+
|
36
|
+
it 'sets force_exclusion to false by default' do
|
37
|
+
expect(options.force_exclusion?).to be false
|
38
|
+
end
|
35
39
|
end
|
36
40
|
|
37
41
|
describe 'parse' do
|
@@ -5,7 +5,7 @@ require_lib 'reek/context/module_context'
|
|
5
5
|
RSpec.describe Reek::Context::CodeContext do
|
6
6
|
context 'name recognition' do
|
7
7
|
let(:ctx) { described_class.new(nil, exp) }
|
8
|
-
let(:exp) {
|
8
|
+
let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode') }
|
9
9
|
let(:exp_name) { 'random_name' }
|
10
10
|
let(:full_name) { "::::::::::::::::::::#{exp_name}" }
|
11
11
|
|
@@ -45,7 +45,7 @@ RSpec.describe Reek::Context::CodeContext do
|
|
45
45
|
context 'when there is an outer' do
|
46
46
|
let(:ctx) { described_class.new(outer, exp) }
|
47
47
|
let(:outer_name) { 'another_random sting' }
|
48
|
-
let(:outer) { described_class.new(nil,
|
48
|
+
let(:outer) { described_class.new(nil, instance_double('Reek::AST::Node')) }
|
49
49
|
|
50
50
|
before do
|
51
51
|
ctx.register_with_parent outer
|
@@ -167,7 +167,7 @@ RSpec.describe Reek::Context::CodeContext do
|
|
167
167
|
let(:expression) { Reek::Source::SourceCode.from(src).syntax_tree }
|
168
168
|
let(:outer) { nil }
|
169
169
|
let(:context) { described_class.new(outer, expression) }
|
170
|
-
let(:sniffer) {
|
170
|
+
let(:sniffer) { class_double('Reek::SmellDetectors::BaseDetector') }
|
171
171
|
|
172
172
|
before do
|
173
173
|
context.register_with_parent(outer)
|
@@ -181,7 +181,7 @@ RSpec.describe Reek::Context::CodeContext do
|
|
181
181
|
end
|
182
182
|
|
183
183
|
context 'when there is an outer context' do
|
184
|
-
let(:outer) { described_class.new(nil,
|
184
|
+
let(:outer) { described_class.new(nil, instance_double('Reek::AST::Node')) }
|
185
185
|
|
186
186
|
before do
|
187
187
|
allow(outer).to receive(:config_for).with(sniffer).and_return(
|
@@ -196,9 +196,9 @@ RSpec.describe Reek::Context::CodeContext do
|
|
196
196
|
end
|
197
197
|
|
198
198
|
describe '#register_with_parent' do
|
199
|
-
let(:context) { described_class.new(nil,
|
200
|
-
let(:first_child) { described_class.new(context,
|
201
|
-
let(:second_child) { described_class.new(context,
|
199
|
+
let(:context) { described_class.new(nil, instance_double('Reek::AST::Node')) }
|
200
|
+
let(:first_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
|
201
|
+
let(:second_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
|
202
202
|
|
203
203
|
it "appends the element to the parent context's list of children" do
|
204
204
|
first_child.register_with_parent context
|
@@ -209,9 +209,9 @@ RSpec.describe Reek::Context::CodeContext do
|
|
209
209
|
end
|
210
210
|
|
211
211
|
describe '#each' do
|
212
|
-
let(:context) { described_class.new(nil,
|
213
|
-
let(:first_child) { described_class.new(context,
|
214
|
-
let(:second_child) { described_class.new(context,
|
212
|
+
let(:context) { described_class.new(nil, instance_double('Reek::AST::Node')) }
|
213
|
+
let(:first_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
|
214
|
+
let(:second_child) { described_class.new(context, instance_double('Reek::AST::Node')) }
|
215
215
|
|
216
216
|
it 'yields each child' do
|
217
217
|
first_child.register_with_parent context
|
@@ -5,7 +5,7 @@ RSpec.describe Reek::Context::MethodContext do
|
|
5
5
|
let(:method_context) { described_class.new(nil, exp) }
|
6
6
|
|
7
7
|
describe '#matches?' do
|
8
|
-
let(:exp) {
|
8
|
+
let(:exp) { instance_double('Reek::AST::SexpExtensions::ModuleNode').as_null_object }
|
9
9
|
|
10
10
|
before do
|
11
11
|
allow(exp).to receive(:full_name).at_least(:once).and_return('mod')
|
@@ -8,7 +8,7 @@ RSpec.describe Reek::Context::ModuleContext do
|
|
8
8
|
module Fred
|
9
9
|
def simple(x) x + 1; end
|
10
10
|
end
|
11
|
-
').to reek_of(:UncommunicativeParameterName, name: 'x')
|
11
|
+
').to reek_of(:UncommunicativeParameterName, name: 'x', context: 'Fred#simple')
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'does not report module with empty class' do
|
@@ -28,9 +28,13 @@ RSpec.describe Reek::Context::ModuleContext do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
describe '#track_visibility' do
|
31
|
-
let(:
|
32
|
-
let(:
|
33
|
-
let(:
|
31
|
+
let(:main_exp) { instance_double('Reek::AST::Node') }
|
32
|
+
let(:first_def) { instance_double('Reek::AST::SexpExtensions::DefNode', name: :foo) }
|
33
|
+
let(:second_def) { instance_double('Reek::AST::SexpExtensions::DefNode') }
|
34
|
+
|
35
|
+
let(:context) { described_class.new(nil, main_exp) }
|
36
|
+
let(:first_child) { Reek::Context::MethodContext.new(context, first_def) }
|
37
|
+
let(:second_child) { Reek::Context::MethodContext.new(context, second_def) }
|
34
38
|
|
35
39
|
it 'sets visibility on subsequent child contexts' do
|
36
40
|
context.append_child_context first_child
|
data/spec/reek/examiner_spec.rb
CHANGED
@@ -28,14 +28,14 @@ RSpec.describe Reek::Examiner do
|
|
28
28
|
context 'with a fragrant String' do
|
29
29
|
let(:examiner) { described_class.new('def good() true; end') }
|
30
30
|
|
31
|
-
|
31
|
+
it_behaves_like 'no smells found'
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'with a smelly String' do
|
35
35
|
let(:examiner) { described_class.new('def fine() y = 4; end') }
|
36
36
|
let(:expected_first_smell) { 'UncommunicativeVariableName' }
|
37
37
|
|
38
|
-
|
38
|
+
it_behaves_like 'one smell found'
|
39
39
|
end
|
40
40
|
|
41
41
|
context 'with a partially masked smelly File' do
|
@@ -48,13 +48,13 @@ RSpec.describe Reek::Examiner do
|
|
48
48
|
let(:path) { CONFIG_PATH.join('partial_mask.reek') }
|
49
49
|
let(:expected_first_smell) { 'UncommunicativeVariableName' }
|
50
50
|
|
51
|
-
|
51
|
+
it_behaves_like 'one smell found'
|
52
52
|
end
|
53
53
|
|
54
54
|
context 'with a fragrant File' do
|
55
55
|
let(:examiner) { described_class.new(CLEAN_FILE) }
|
56
56
|
|
57
|
-
|
57
|
+
it_behaves_like 'no smells found'
|
58
58
|
end
|
59
59
|
|
60
60
|
describe '.new' do
|
@@ -16,19 +16,19 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
|
|
16
16
|
source: 'a/ruby/source/file.rb')
|
17
17
|
end
|
18
18
|
|
19
|
-
it '
|
19
|
+
it 'computes the fingerprint' do
|
20
20
|
expect(computed).to eq expected_fingerprint
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
context 'with code at a specific location' do
|
25
25
|
let(:lines) { [1] }
|
26
|
-
|
26
|
+
it_behaves_like 'computes a fingerprint with no parameters'
|
27
27
|
end
|
28
28
|
|
29
29
|
context 'with code at a different location' do
|
30
30
|
let(:lines) { [5] }
|
31
|
-
|
31
|
+
it_behaves_like 'computes a fingerprint with no parameters'
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'when the fingerprint should not be computed' do
|
@@ -41,7 +41,7 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
|
|
41
41
|
source: 'a/ruby/source/file.rb')
|
42
42
|
end
|
43
43
|
|
44
|
-
it '
|
44
|
+
it 'returns nil' do
|
45
45
|
expect(computed).to be_nil
|
46
46
|
end
|
47
47
|
end
|
@@ -66,14 +66,14 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
|
|
66
66
|
let(:name) { 'bravo' }
|
67
67
|
let(:expected_fingerprint) { '9c3fd378178118a67e9509f87cae24f9' }
|
68
68
|
|
69
|
-
|
69
|
+
it_behaves_like 'computes a fingerprint with identifying parameters'
|
70
70
|
end
|
71
71
|
|
72
72
|
context 'when the name is another thing it has another fingerprint' do
|
73
73
|
let(:name) { 'echo' }
|
74
74
|
let(:expected_fingerprint) { 'd2a6d2703ce04cca65e7300b7de4b89f' }
|
75
75
|
|
76
|
-
|
76
|
+
it_behaves_like 'computes a fingerprint with identifying parameters'
|
77
77
|
end
|
78
78
|
|
79
79
|
shared_examples_for 'computes a fingerprint with identifying and non-identifying parameters' do
|
@@ -98,7 +98,7 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
|
|
98
98
|
let(:lines) { [1, 7, 10, 13, 15] }
|
99
99
|
let(:expected_fingerprint) { '238733f4f51ba5473dcbe94a43ec5400' }
|
100
100
|
|
101
|
-
|
101
|
+
it_behaves_like 'computes a fingerprint with identifying and non-identifying parameters'
|
102
102
|
end
|
103
103
|
|
104
104
|
context 'when the non-identifying parameters change, it computes the same fingerprint' do
|
@@ -107,7 +107,7 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
|
|
107
107
|
let(:lines) { [1, 7, 10, 13, 15, 17, 19, 20, 25] }
|
108
108
|
let(:expected_fingerprint) { '238733f4f51ba5473dcbe94a43ec5400' }
|
109
109
|
|
110
|
-
|
110
|
+
it_behaves_like 'computes a fingerprint with identifying and non-identifying parameters'
|
111
111
|
end
|
112
112
|
|
113
113
|
context 'but when the identifying parameters change, it computes a different fingerprint' do
|
@@ -116,7 +116,7 @@ RSpec.describe Reek::Report::CodeClimateFingerprint do
|
|
116
116
|
let(:lines) { [1, 7, 10, 13, 15] }
|
117
117
|
let(:expected_fingerprint) { 'e0c35e9223cc19bdb9a04fb3e60573e1' }
|
118
118
|
|
119
|
-
|
119
|
+
it_behaves_like 'computes a fingerprint with identifying and non-identifying parameters'
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
@@ -30,14 +30,14 @@ RSpec.describe Reek::SmellWarning do
|
|
30
30
|
let(:first) { build(:smell_warning, smell_detector: duplication_detector) }
|
31
31
|
let(:second) { build(:smell_warning, smell_detector: feature_envy_detector) }
|
32
32
|
|
33
|
-
|
33
|
+
it_behaves_like 'first sorts ahead of second'
|
34
34
|
end
|
35
35
|
|
36
36
|
context 'smells differing only by lines' do
|
37
37
|
let(:first) { build(:smell_warning, smell_detector: feature_envy_detector, lines: [2]) }
|
38
38
|
let(:second) { build(:smell_warning, smell_detector: feature_envy_detector, lines: [3]) }
|
39
39
|
|
40
|
-
|
40
|
+
it_behaves_like 'first sorts ahead of second'
|
41
41
|
end
|
42
42
|
|
43
43
|
context 'smells differing only by context' do
|
@@ -46,7 +46,7 @@ RSpec.describe Reek::SmellWarning do
|
|
46
46
|
build(:smell_warning, smell_detector: duplication_detector, context: 'second')
|
47
47
|
end
|
48
48
|
|
49
|
-
|
49
|
+
it_behaves_like 'first sorts ahead of second'
|
50
50
|
end
|
51
51
|
|
52
52
|
context 'smells differing only by message' do
|
@@ -59,7 +59,7 @@ RSpec.describe Reek::SmellWarning do
|
|
59
59
|
context: 'ctx', message: 'second message')
|
60
60
|
end
|
61
61
|
|
62
|
-
|
62
|
+
it_behaves_like 'first sorts ahead of second'
|
63
63
|
end
|
64
64
|
|
65
65
|
context 'smell name takes precedence over message' do
|
@@ -70,7 +70,7 @@ RSpec.describe Reek::SmellWarning do
|
|
70
70
|
build(:smell_warning, smell_detector: utility_function_detector, message: 'first message')
|
71
71
|
end
|
72
72
|
|
73
|
-
|
73
|
+
it_behaves_like 'first sorts ahead of second'
|
74
74
|
end
|
75
75
|
|
76
76
|
context 'smells differing everywhere' do
|
@@ -86,7 +86,7 @@ RSpec.describe Reek::SmellWarning do
|
|
86
86
|
message: "has the variable name '@s'")
|
87
87
|
end
|
88
88
|
|
89
|
-
|
89
|
+
it_behaves_like 'first sorts ahead of second'
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -26,36 +26,15 @@ RSpec.describe Reek::Source::SourceCode do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
context 'when the parser fails' do
|
29
|
-
let(:catcher) { StringIO.new }
|
30
29
|
let(:source_name) { 'Test source' }
|
31
30
|
let(:error_message) { 'Error message' }
|
32
31
|
let(:parser) { class_double(Parser::Ruby23) }
|
33
32
|
let(:src) { described_class.new(code: '', origin: source_name, parser: parser) }
|
34
33
|
|
35
|
-
before { $stderr = catcher }
|
36
|
-
|
37
34
|
shared_examples_for 'handling and recording the error' do
|
38
|
-
it '
|
39
|
-
src.syntax_tree
|
40
|
-
|
41
|
-
|
42
|
-
it 'returns an empty syntax tree' do
|
43
|
-
expect(src.syntax_tree).to be_nil
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'records the syntax error' do
|
47
|
-
src.syntax_tree
|
48
|
-
expect(catcher.string).to match(error_class.name)
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'records the source name' do
|
52
|
-
src.syntax_tree
|
53
|
-
expect(catcher.string).to match(source_name)
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'records the error message' do
|
57
|
-
src.syntax_tree
|
58
|
-
expect(catcher.string).to match(error_message)
|
35
|
+
it 'raises an informative error' do
|
36
|
+
expect { src.syntax_tree }.
|
37
|
+
to raise_error(/#{source_name}: #{error_class.name}: #{error_message}/)
|
59
38
|
end
|
60
39
|
end
|
61
40
|
|
@@ -68,7 +47,7 @@ RSpec.describe Reek::Source::SourceCode do
|
|
68
47
|
and_raise error_class.new(diagnostic)
|
69
48
|
end
|
70
49
|
|
71
|
-
|
50
|
+
it_behaves_like 'handling and recording the error'
|
72
51
|
end
|
73
52
|
|
74
53
|
context 'with a Racc::ParseError' do
|
@@ -79,7 +58,7 @@ RSpec.describe Reek::Source::SourceCode do
|
|
79
58
|
and_raise(error_class.new(error_message))
|
80
59
|
end
|
81
60
|
|
82
|
-
|
61
|
+
it_behaves_like 'handling and recording the error'
|
83
62
|
end
|
84
63
|
|
85
64
|
context 'with a generic error' do
|
@@ -91,10 +70,8 @@ RSpec.describe Reek::Source::SourceCode do
|
|
91
70
|
end
|
92
71
|
|
93
72
|
it 'raises the error' do
|
94
|
-
expect { src.syntax_tree }.to raise_error
|
73
|
+
expect { src.syntax_tree }.to raise_error error_class
|
95
74
|
end
|
96
75
|
end
|
97
|
-
|
98
|
-
after { $stderr = STDERR }
|
99
76
|
end
|
100
77
|
end
|
@@ -29,34 +29,66 @@ RSpec.describe Reek::Source::SourceLocator do
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
# rubocop:disable RSpec/NestedGroups
|
32
33
|
context 'exclude paths' do
|
33
34
|
let(:configuration) do
|
34
35
|
test_configuration_for(CONFIG_PATH.join('with_excluded_paths.reek'))
|
35
36
|
end
|
36
37
|
|
37
|
-
let(:
|
38
|
+
let(:options) { instance_double('Reek::CLI::Options', force_exclusion?: false) }
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
end
|
40
|
+
context 'when the path is a file name in an excluded directory' do
|
41
|
+
let(:path) { SAMPLES_PATH.join('source_with_exclude_paths', 'ignore_me', 'uncommunicative_method_name.rb') }
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
]
|
48
|
-
end
|
43
|
+
context 'when options.force_exclusion? is true' do
|
44
|
+
before do
|
45
|
+
allow(options).to receive(:force_exclusion?).and_return(true)
|
46
|
+
end
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
it 'excludes this file' do
|
49
|
+
sources = described_class.new([path], configuration: configuration, options: options).sources
|
50
|
+
expect(sources).not_to include(path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when options.force_exclusion? is false' do
|
55
|
+
before do
|
56
|
+
allow(options).to receive(:force_exclusion?).and_return(false)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'includes this file' do
|
60
|
+
sources = described_class.new([path], configuration: configuration, options: options).sources
|
61
|
+
expect(sources).to include(path)
|
62
|
+
end
|
63
|
+
end
|
53
64
|
end
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
|
66
|
+
context 'when path is a directory' do
|
67
|
+
let(:path) { SAMPLES_PATH.join('source_with_exclude_paths') }
|
68
|
+
|
69
|
+
let(:expected_paths) do
|
70
|
+
[path.join('nested/uncommunicative_parameter_name.rb')]
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:paths_that_are_expected_to_be_ignored) do
|
74
|
+
[
|
75
|
+
path.join('ignore_me/uncommunicative_method_name.rb'),
|
76
|
+
path.join('nested/ignore_me_as_well/irresponsible_module.rb')
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does not use excluded paths' do
|
81
|
+
sources = described_class.new([path], configuration: configuration, options: options).sources
|
82
|
+
expect(sources).not_to include(*paths_that_are_expected_to_be_ignored)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'scans directories that are not excluded' do
|
86
|
+
sources = described_class.new([path], configuration: configuration).sources
|
87
|
+
expect(sources).to eq expected_paths
|
88
|
+
end
|
58
89
|
end
|
59
90
|
end
|
91
|
+
# rubocop:enable RSpec/NestedGroups
|
60
92
|
|
61
93
|
context 'non-Ruby paths' do
|
62
94
|
let(:path) { SAMPLES_PATH.join('source_with_non_ruby_files') }
|
@@ -36,14 +36,14 @@ RSpec.describe Reek::Spec::ShouldReekOnlyOf do
|
|
36
36
|
context 'with no smells' do
|
37
37
|
let(:smells) { [] }
|
38
38
|
|
39
|
-
|
39
|
+
it_behaves_like 'no match'
|
40
40
|
end
|
41
41
|
|
42
42
|
context 'with 1 non-matching smell' do
|
43
43
|
let(:control_couple_detector) { build(:smell_detector, smell_type: 'ControlParameter') }
|
44
44
|
let(:smells) { [build(:smell_warning, smell_detector: control_couple_detector)] }
|
45
45
|
|
46
|
-
|
46
|
+
it_behaves_like 'no match'
|
47
47
|
end
|
48
48
|
|
49
49
|
context 'with 2 non-matching smells' do
|
@@ -56,7 +56,7 @@ RSpec.describe Reek::Spec::ShouldReekOnlyOf do
|
|
56
56
|
]
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
it_behaves_like 'no match'
|
60
60
|
end
|
61
61
|
|
62
62
|
context 'with 1 non-matching and 1 matching smell' do
|
@@ -70,7 +70,7 @@ RSpec.describe Reek::Spec::ShouldReekOnlyOf do
|
|
70
70
|
]
|
71
71
|
end
|
72
72
|
|
73
|
-
|
73
|
+
it_behaves_like 'no match'
|
74
74
|
end
|
75
75
|
|
76
76
|
context 'with 1 matching smell' do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reek
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Rutherford
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2017-
|
14
|
+
date: 2017-04-04 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: codeclimate-engine-rb
|
@@ -229,6 +229,7 @@ files:
|
|
229
229
|
- lib/reek/errors/base_error.rb
|
230
230
|
- lib/reek/errors/garbage_detector_configuration_in_comment_error.rb
|
231
231
|
- lib/reek/errors/incomprehensible_source_error.rb
|
232
|
+
- lib/reek/errors/parse_error.rb
|
232
233
|
- lib/reek/examiner.rb
|
233
234
|
- lib/reek/logging_error_handler.rb
|
234
235
|
- lib/reek/rake/task.rb
|
@@ -438,7 +439,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
438
439
|
version: '0'
|
439
440
|
requirements: []
|
440
441
|
rubyforge_project:
|
441
|
-
rubygems_version: 2.
|
442
|
+
rubygems_version: 2.5.1
|
442
443
|
signing_key:
|
443
444
|
specification_version: 4
|
444
445
|
summary: Code smell detector for Ruby
|