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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c4b9b3b7dc545af9ebbca5f6f24442fa31e6082
4
- data.tar.gz: 8deb694c3389aeb962fbeef331eab9ba4706ee0e
3
+ metadata.gz: 5c827e3718e3a4f1524fdaf0d6c28df6e86b671b
4
+ data.tar.gz: 9755661abe970fb1499ee2b9de2f6d701b8c8ee2
5
5
  SHA512:
6
- metadata.gz: d8fc3081171e7e5cf12b19b4ba8c1a46590d5d197d8baf6373a759a88f90f092da133282d50f7a292bcb0fe812c6890d3db87c02b1620af434b8d3c579aa3592
7
- data.tar.gz: 2474e384c11004a10ede8186caa46a504a0d5a8fdc9b2e5ded5b8fd6bd34b3ffc3f922c04ffce0ed75530002f8fc11ba2bd73ea047a8feb9c5a7dfd7149cbbbf
6
+ metadata.gz: 77122032bb96bbd32994195c9745acc214ecfbfff7963348aff01239402deefb74032883eed3762df8eda34c6054447a5ca076dfe707a9a1460a5c14bc85ddf7
7
+ data.tar.gz: e86a819d479502f6f091c6d6f8b7d1b6c1aae20779131a3e6e62445eb048db3f1f0e1226b59a0b11ca82e9f3c4471578ce4c71c19d0a1fdff96b51f8f5e7d752
@@ -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:
@@ -1,5 +1,10 @@
1
1
  # Change log
2
2
 
3
+ ## 4.6.0 (2017-04-04)
4
+
5
+ * (IanWhitney) Implement `--force-exclusion` flag
6
+ * (mvz) Raise Reek-specific error on parse errors
7
+
3
8
  ## 4.5.6 (2017-02-17)
4
9
 
5
10
  * (mvz) Raise on errors inside of Examiner#smells instead of outputting to STDERR
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.13.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.10.0'
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 tests (RSpec and Cucumber), RuboCop and Reek itself.
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
- Then it succeeds
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
@@ -1,5 +1,4 @@
1
- Feature:
2
-
1
+ Feature: Auto-generate a todo file
3
2
  Write a Reek configuration as a kind of todo list that will prevent Reek
4
3
  from reporting smells on the current code.
5
4
  This can then be worked on later on.
@@ -8,11 +8,6 @@ module Reek
8
8
  children.first
9
9
  end
10
10
 
11
- # Other is a symbol?
12
- def ==(other)
13
- name == other
14
- end
15
-
16
11
  def marked_unused?
17
12
  plain_name.start_with?('_')
18
13
  end
@@ -4,6 +4,7 @@ module Reek
4
4
  module SexpExtensions
5
5
  # Utility methods for :const nodes.
6
6
  module ConstNode
7
+ # TODO: name -> full_name, simple_name -> name
7
8
  def name
8
9
  if namespace
9
10
  "#{namespace.format_to_ruby}::#{simple_name}"
@@ -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
@@ -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: 11 }
15
- # :reek:TooManyMethods: { max_methods: 17 }
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: 6 }
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
- $stderr.puts "#{origin}: #{error.class.name}: #{error}"
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
@@ -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
@@ -7,6 +7,6 @@ module Reek
7
7
  # @public
8
8
  module Version
9
9
  # @public
10
- STRING = '4.5.6'.freeze
10
+ STRING = '4.6.0'.freeze
11
11
  end
12
12
  end
@@ -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
- $stdout = StringIO.new
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
- before do
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
- 'NilCheck' => { 'exclude' => ['Foo#bar'] },
96
- 'NestedIterators' => { 'exclude' => ['Bar#baz'] }
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
- before do
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) { double('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, double('exp1')) }
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) { double('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, double('exp1')) }
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, double('exp1')) }
200
- let(:first_child) { described_class.new(context, double('exp2')) }
201
- let(:second_child) { described_class.new(context, double('exp3')) }
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, double('exp1')) }
213
- let(:first_child) { described_class.new(context, double('exp2')) }
214
- let(:second_child) { described_class.new(context, double('exp3')) }
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) { double('exp').as_null_object }
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(:context) { described_class.new(nil, double('exp1')) }
32
- let(:first_child) { Reek::Context::MethodContext.new(context, double('exp2', type: :def, name: :foo)) }
33
- let(:second_child) { Reek::Context::MethodContext.new(context, double('exp3', type: :def)) }
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
@@ -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
- it_should_behave_like 'no smells found'
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
- it_should_behave_like 'one smell found'
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
- it_should_behave_like 'one smell found'
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
- it_should_behave_like 'no smells found'
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 'it computes the fingerprint' do
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
- it_should_behave_like 'computes a fingerprint with no parameters'
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
- it_should_behave_like 'computes a fingerprint with no parameters'
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 'it returns nil' do
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
- it_should_behave_like 'computes a fingerprint with identifying parameters'
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
- it_should_behave_like 'computes a fingerprint with identifying parameters'
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
- it_should_behave_like 'computes a fingerprint with identifying and non-identifying parameters'
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
- it_should_behave_like 'computes a fingerprint with identifying and non-identifying parameters'
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
- it_should_behave_like 'computes a fingerprint with identifying and non-identifying parameters'
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
- it_should_behave_like 'first sorts ahead of second'
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
- it_should_behave_like 'first sorts ahead of second'
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
- it_should_behave_like 'first sorts ahead of second'
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
- it_should_behave_like 'first sorts ahead of second'
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
- it_should_behave_like 'first sorts ahead of second'
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
- it_should_behave_like 'first sorts ahead of second'
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 'does not raise an error' do
39
- src.syntax_tree
40
- end
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
- it_should_behave_like 'handling and recording the error'
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
- it_should_behave_like 'handling and recording the error'
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(:path) { SAMPLES_PATH.join('source_with_exclude_paths') }
38
+ let(:options) { instance_double('Reek::CLI::Options', force_exclusion?: false) }
38
39
 
39
- let(:expected_paths) do
40
- [path.join('nested/uncommunicative_parameter_name.rb')]
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
- let(:paths_that_are_expected_to_be_ignored) do
44
- [
45
- path.join('ignore_me/uncommunicative_method_name.rb'),
46
- path.join('nested/ignore_me_as_well/irresponsible_module.rb')
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
- it 'does not use excluded paths' do
51
- sources = described_class.new([path], configuration: configuration).sources
52
- expect(sources).not_to include(*paths_that_are_expected_to_be_ignored)
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
- it 'scans directories that are not excluded' do
56
- sources = described_class.new([path], configuration: configuration).sources
57
- expect(sources).to eq expected_paths
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
- it_should_behave_like 'no match'
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
- it_should_behave_like 'no match'
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
- it_should_behave_like 'no match'
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
- it_should_behave_like 'no match'
73
+ it_behaves_like 'no match'
74
74
  end
75
75
 
76
76
  context 'with 1 matching smell' do
@@ -1,4 +1,5 @@
1
1
  require 'pathname'
2
+ require 'timeout'
2
3
  require_relative '../lib/reek'
3
4
  require_relative '../lib/reek/spec'
4
5
  require_relative '../lib/reek/ast/ast_node_class_map'
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.5.6
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-02-18 00:00:00.000000000 Z
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.6.8
442
+ rubygems_version: 2.5.1
442
443
  signing_key:
443
444
  specification_version: 4
444
445
  summary: Code smell detector for Ruby