rubocop-rspec 1.24.0 → 1.25.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +1 -2
  4. data/README.md +7 -4
  5. data/Rakefile +18 -3
  6. data/config/default.yml +25 -0
  7. data/lib/rubocop-rspec.rb +3 -0
  8. data/lib/rubocop/cop/rspec/be.rb +35 -0
  9. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +34 -0
  10. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -2
  11. data/lib/rubocop/cop/rspec/empty_example_group.rb +3 -3
  12. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -2
  13. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +148 -0
  14. data/lib/rubocop/cop/rspec/factory_bot/dynamic_attribute_defined_statically.rb +1 -3
  15. data/lib/rubocop/cop/rspec/factory_bot/static_attribute_defined_dynamically.rb +3 -3
  16. data/lib/rubocop/cop/rspec/instance_variable.rb +2 -2
  17. data/lib/rubocop/cop/rspec/multiple_expectations.rb +2 -2
  18. data/lib/rubocop/cop/rspec/nested_groups.rb +6 -3
  19. data/lib/rubocop/cop/rspec/pending.rb +71 -0
  20. data/lib/rubocop/cop/rspec/predicate_matcher.rb +11 -13
  21. data/lib/rubocop/cop/rspec/return_from_stub.rb +9 -16
  22. data/lib/rubocop/cop/rspec/shared_examples.rb +76 -0
  23. data/lib/rubocop/cop/rspec_cops.rb +4 -0
  24. data/lib/rubocop/rspec/example.rb +1 -1
  25. data/lib/rubocop/rspec/node.rb +19 -0
  26. data/lib/rubocop/rspec/top_level_describe.rb +3 -6
  27. data/lib/rubocop/rspec/version.rb +1 -1
  28. data/rubocop-rspec.gemspec +6 -1
  29. data/spec/rubocop/cop/rspec/be_spec.rb +33 -0
  30. data/spec/rubocop/cop/rspec/capybara/feature_methods_spec.rb +75 -18
  31. data/spec/rubocop/cop/rspec/cop_spec.rb +0 -4
  32. data/spec/rubocop/cop/rspec/described_class_spec.rb +1 -1
  33. data/spec/rubocop/cop/rspec/example_without_description_spec.rb +8 -0
  34. data/spec/rubocop/cop/rspec/factory_bot/create_list_spec.rb +140 -0
  35. data/spec/rubocop/cop/rspec/factory_bot/dynamic_attribute_defined_statically_spec.rb +11 -1
  36. data/spec/rubocop/cop/rspec/nested_groups_spec.rb +15 -0
  37. data/spec/rubocop/cop/rspec/pending_spec.rb +162 -0
  38. data/spec/rubocop/cop/rspec/predicate_matcher_spec.rb +13 -9
  39. data/spec/rubocop/cop/rspec/return_from_stub_spec.rb +9 -0
  40. data/spec/rubocop/cop/rspec/shared_examples_spec.rb +93 -0
  41. data/spec/spec_helper.rb +1 -1
  42. metadata +19 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa8c9ef29194d2c818e26ab7304672b410755e182a6ff7476f58f933be307102
4
- data.tar.gz: 6bd4ae51e5e90ea1dcb4217a4f741fa67b4a503dfd350205a6950b0cd2305397
3
+ metadata.gz: c195bc91134a73125f33d0675230a01fd467747690f3113ff3a924559d7c748c
4
+ data.tar.gz: 3f9fcaedd22ab9f67542b083ef72a6ab466df04a758c84f3e244176b781a6109
5
5
  SHA512:
6
- metadata.gz: 70867c1789297dc3408df72fd6a0e831afec0238de3250c5446ce3385100f52ee3cc5962b2d3d4263ce4057bda07eddfc3f365921ce1049926030d0c3b21490c
7
- data.tar.gz: 9d91b68565b10ba04221e4a516359e1bbc942e6e955bc4076937ae0c45fb317780b90c9d502e4e5eff5898b47d3e3fd9324cc00beb7613a33ffcb3f5f64c733e
6
+ metadata.gz: be3574c7d021a099bdd2869d47a908f4ac5c3ea9e12823030db025a92d24fd3d8008373b422b72e7e666cfefda0a983b3ae6d5f57e62c2dd14f99dc416d48ac3
7
+ data.tar.gz: 8bcad630c6244f19a896c061c15c94dbe9611f651d72306642bb980c2aa082b0d78d54f54e3b32544b475c4cbbb8a18dbe8631105cdbec4f6d12e0b81851b8b2
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 1.25.0 (2018-04-07)
6
+
7
+ * Add `RSpec/SharedExamples` cop to enforce consistent usage of string to titleize shared examples. ([@anthony-robin][])
8
+ * Add `RSpec/Be` cop to enforce passing argument to the generic `be` matcher. ([@Darhazer][])
9
+ * Fix false positives in `StaticAttributeDefinedDynamically` and `ReturnFromStub` when a const is used in an array or hash. ([@Darhazer][])
10
+ * Add `RSpec/Pending` cop to enforce no existing pending or skipped examples. This is disabled by default. ([@patrickomatic][])
11
+ * Fix `RSpec/NestedGroups` cop support --auto-gen-config. ([@walf443][])
12
+ * Fix false positives in `Capybara/FeatureMethods` when feature methods are used as property names in a factory. ([@Darhazer][])
13
+ * Allow configuring enabled methods in `Capybara/FeatureMethods`. ([@Darhazer][])
14
+ * Add `FactoryBot/CreateList` cop. ([@Darhazer][])
15
+
5
16
  ## 1.24.0 (2018-03-06)
6
17
 
7
18
  * Compatibility with RuboCop v0.53.0. ([@bquorning][])
@@ -316,3 +327,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
316
327
  [@anthony-robin]: https://github.com/anthony-robin
317
328
  [@jojos003]: https://github.com/jojos003
318
329
  [@abrom]: https://github.com/abrom
330
+ [@patrickomatic]: https://github.com/patrickomatic
data/Gemfile CHANGED
@@ -3,8 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :test do
6
- gem 'codeclimate-test-reporter', '~> 1.0.0'
7
- gem 'simplecov', '~> 0.12.0', require: false
6
+ gem 'simplecov', require: false
8
7
  end
9
8
 
10
9
  local_gemfile = 'Gemfile.local'
data/README.md CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  [![Join the chat at https://gitter.im/rubocop-rspec/Lobby](https://badges.gitter.im/rubocop-rspec/Lobby.svg)](https://gitter.im/rubocop-rspec/Lobby)
4
4
  [![Gem Version](https://badge.fury.io/rb/rubocop-rspec.svg)](https://rubygems.org/gems/rubocop-rspec)
5
- [![Dependency Status](https://gemnasium.com/backus/rubocop-rspec.svg)](https://gemnasium.com/backus/rubocop-rspec)
6
- [![Build Status](https://secure.travis-ci.org/backus/rubocop-rspec.svg?branch=master)](http://travis-ci.org/backus/rubocop-rspec)
7
- [![Coverage Status](https://codeclimate.com/github/backus/rubocop-rspec/badges/coverage.svg)](https://codeclimate.com/github/backus/rubocop-rspec/coverage)
8
- [![Code Climate](https://codeclimate.com/github/backus/rubocop-rspec/badges/gpa.svg)](https://codeclimate.com/github/backus/rubocop-rspec)
5
+ [![CircleCI](https://circleci.com/gh/rubocop-rspec/rubocop-rspec.svg?style=svg)](https://circleci.com/gh/rubocop-rspec/rubocop-rspec)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/f6254deb61671e357f30/test_coverage)](https://codeclimate.com/github/rubocop-rspec/rubocop-rspec/test_coverage)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/f6254deb61671e357f30/maintainability)](https://codeclimate.com/github/rubocop-rspec/rubocop-rspec/maintainability)
9
8
 
10
9
  RSpec-specific analysis for your projects, as an extension to
11
10
  [RuboCop](https://github.com/bbatsov/rubocop).
@@ -60,6 +59,10 @@ end
60
59
 
61
60
  rubocop-rspec is available on Code Climate as part of the rubocop engine. [Learn More](https://codeclimate.com/changelog/55a433bbe30ba00852000fac).
62
61
 
62
+ ## Documentation
63
+
64
+ You can read more about RuboCop-RSpec in its [official manual](http://rubocop-rspec.readthedocs.io).
65
+
63
66
  ## Inspecting files that don't end with `_spec.rb`
64
67
 
65
68
  By default, `rubocop-rspec` only inspects code within paths ending in `_spec.rb` or including `spec/`. You can override this setting in your config file by specifying one or more patterns:
data/Rakefile CHANGED
@@ -12,6 +12,9 @@ rescue Bundler::BundlerError => e
12
12
  end
13
13
 
14
14
  require 'rspec/core/rake_task'
15
+
16
+ Dir['tasks/**/*.rake'].each { |t| load t }
17
+
15
18
  RSpec::Core::RakeTask.new(:spec) do |spec|
16
19
  spec.pattern = FileList['spec/**/*_spec.rb']
17
20
  end
@@ -20,8 +23,6 @@ desc 'Run RSpec with code coverage'
20
23
  task :coverage do
21
24
  ENV['COVERAGE'] = 'true'
22
25
  Rake::Task['spec'].execute
23
-
24
- sh('codeclimate-test-reporter') if ENV['CI']
25
26
  end
26
27
 
27
28
  desc 'Run RuboCop over this gem'
@@ -44,7 +45,21 @@ task confirm_config: :build_config do
44
45
  end
45
46
  end
46
47
 
47
- task default: %i[build_config coverage internal_investigation confirm_config]
48
+ desc 'Confirm documentation is up to date'
49
+ task confirm_documentation: :generate_cops_documentation do
50
+ _, _, _, process =
51
+ Open3.popen3('git diff --exit-code manual/')
52
+
53
+ unless process.value.success?
54
+ raise 'manual is out of sync, please add manual/ to the commit'
55
+ end
56
+ end
57
+
58
+ task default: %i[build_config coverage
59
+ internal_investigation
60
+ confirm_config
61
+ documentation_syntax_check
62
+ confirm_documentation]
48
63
 
49
64
  desc 'Generate a new cop template'
50
65
  task :new_cop, [:cop] do |_task, args|
@@ -29,6 +29,11 @@ RSpec/AlignRightLetBrace:
29
29
  Enabled: false
30
30
  StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/AlignRightLetBrace
31
31
 
32
+ RSpec/Be:
33
+ Description: Check for expectations where `be` is used without argument.
34
+ Enabled: true
35
+ StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Be
36
+
32
37
  RSpec/BeEql:
33
38
  Description: Check for expectations where `be(...)` can replace `eql(...)`.
34
39
  Enabled: true
@@ -289,6 +294,11 @@ RSpec/OverwritingSetup:
289
294
  Description: Checks if there is a let/subject that overwrites an existing one.
290
295
  StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/OverwritingSetup
291
296
 
297
+ RSpec/Pending:
298
+ Enabled: false
299
+ Description: Checks for any pending or skipped examples.
300
+ StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Pending
301
+
292
302
  RSpec/RepeatedDescription:
293
303
  Enabled: true
294
304
  Description: Check for repeated description strings in example groups.
@@ -313,6 +323,11 @@ RSpec/SharedContext:
313
323
  Enabled: true
314
324
  StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedContext
315
325
 
326
+ RSpec/SharedExamples:
327
+ Description: Enforces use of string to titleize shared examples.
328
+ Enabled: true
329
+ StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/SharedExamples
330
+
316
331
  RSpec/SingleArgumentMessageChain:
317
332
  Description: Checks that chains of messages contain more than one element.
318
333
  Enabled: true
@@ -362,8 +377,18 @@ Capybara/CurrentPathExpectation:
362
377
  Capybara/FeatureMethods:
363
378
  Description: Checks for consistent method usage in feature specs.
364
379
  Enabled: true
380
+ EnabledMethods: []
365
381
  StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Capybara/FeatureMethods
366
382
 
383
+ FactoryBot/CreateList:
384
+ Description: Checks for create_list usage.
385
+ Enabled: true
386
+ EnforcedStyle: create_list
387
+ SupportedStyles:
388
+ - create_list
389
+ - n_times
390
+ StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/FactoryBot/CreateList
391
+
367
392
  FactoryBot/DynamicAttributeDefinedStatically:
368
393
  Description: Prefer declaring dynamic attribute values in a block.
369
394
  Enabled: true
@@ -6,6 +6,7 @@ require 'rubocop'
6
6
  require_relative 'rubocop/rspec'
7
7
  require_relative 'rubocop/rspec/version'
8
8
  require_relative 'rubocop/rspec/inject'
9
+ require_relative 'rubocop/rspec/node'
9
10
  require_relative 'rubocop/rspec/top_level_describe'
10
11
  require_relative 'rubocop/rspec/wording'
11
12
  require_relative 'rubocop/rspec/util'
@@ -38,3 +39,5 @@ module RuboCop
38
39
  end
39
40
  end
40
41
  end
42
+
43
+ RuboCop::AST::Node.include(RuboCop::RSpec::Node)
@@ -0,0 +1,35 @@
1
+ module RuboCop
2
+ module Cop
3
+ module RSpec
4
+ # Check for expectations where `be` is used without argument.
5
+ #
6
+ # The `be` matcher is too generic, as it pass on everything that is not
7
+ # nil or false. If that is the exact intend, use `be_truthy`. In all other
8
+ # cases it's better to specify what exactly is the expected value.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # expect(foo).to be
14
+ #
15
+ # # good
16
+ # expect(foo).to be_truthy
17
+ # expect(foo).to be 1.0
18
+ # expect(foo).to be(true)
19
+ #
20
+ class Be < Cop
21
+ MSG = 'Don\'t use `be` without an argument.'.freeze
22
+
23
+ def_node_matcher :be_without_args, <<-PATTERN
24
+ (send _ {:to :not_to :to_not} $(send nil? :be))
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ be_without_args(node) do |matcher|
29
+ add_offense(matcher, location: :selector)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -6,6 +6,14 @@ module RuboCop
6
6
  module Capybara
7
7
  # Checks for consistent method usage in feature specs.
8
8
  #
9
+ # By default, the cop disables all Capybara-specific methods that have
10
+ # the same native RSpec method (e.g. are just aliases). Some teams
11
+ # however may prefer using some of the Capybara methods (like `feature`)
12
+ # to make it obvious that the test uses Capybara, while still disable
13
+ # the rest of the methods, like `given` (alias for `let`), `background`
14
+ # (alias for `before`), etc. You can configure which of the methods to
15
+ # be enabled by using the EnabledMethods configuration option.
16
+ #
9
17
  # @example
10
18
  # # bad
11
19
  # feature 'User logs in' do
@@ -45,6 +53,12 @@ module RuboCop
45
53
  feature: :describe
46
54
  }.freeze
47
55
 
56
+ def_node_matcher :spec?, <<-PATTERN
57
+ (block
58
+ (send {(const nil? :RSpec) nil?} {:describe :feature} ...)
59
+ ...)
60
+ PATTERN
61
+
48
62
  def_node_matcher :feature_method, <<-PATTERN
49
63
  (block
50
64
  $(send {(const nil? :RSpec) nil?} ${#{MAP.keys.map(&:inspect).join(' ')}} ...)
@@ -52,7 +66,11 @@ module RuboCop
52
66
  PATTERN
53
67
 
54
68
  def on_block(node)
69
+ return unless spec?(root_node)
70
+
55
71
  feature_method(node) do |send_node, match|
72
+ next if enabled?(match)
73
+
56
74
  add_offense(
57
75
  send_node,
58
76
  location: :selector,
@@ -66,6 +84,22 @@ module RuboCop
66
84
  corrector.replace(node.loc.selector, MAP[node.method_name].to_s)
67
85
  end
68
86
  end
87
+
88
+ private
89
+
90
+ def root_node
91
+ processed_source.ast
92
+ end
93
+
94
+ def enabled?(method_name)
95
+ enabled_methods.include?(method_name)
96
+ end
97
+
98
+ def enabled_methods
99
+ cop_config
100
+ .fetch('EnabledMethods', [])
101
+ .map(&:to_sym)
102
+ end
69
103
  end
70
104
  end
71
105
  end
@@ -8,12 +8,12 @@ module RuboCop
8
8
  # @example
9
9
  # # bad
10
10
  # describe :my_method do
11
- # ...
11
+ # # ...
12
12
  # end
13
13
  #
14
14
  # # good
15
15
  # describe '#my_method' do
16
- # ...
16
+ # # ...
17
17
  # end
18
18
  #
19
19
  # @see https://github.com/rspec/rspec-core/issues/1610
@@ -36,9 +36,9 @@ module RuboCop
36
36
  # @example configuration
37
37
  #
38
38
  # # .rubocop.yml
39
- # RSpec/EmptyExampleGroup:
40
- # CustomIncludeMethods:
41
- # - include_tests
39
+ # # RSpec/EmptyExampleGroup:
40
+ # # CustomIncludeMethods:
41
+ # # - include_tests
42
42
  #
43
43
  # # spec_helper.rb
44
44
  # RSpec.configure do |config|
@@ -72,8 +72,7 @@ module RuboCop
72
72
  private
73
73
 
74
74
  def check_example_without_description(node)
75
- _send, _method, arg = *node
76
- return unless arg.nil?
75
+ return if node.arguments?
77
76
  return unless disallow_empty_description?(node)
78
77
 
79
78
  add_offense(node, message: MSG_ADD_DESCRIPTION)
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module FactoryBot
7
+ # Checks for create_list usage.
8
+ #
9
+ # This cop can be configured using the `EnforcedStyle` option
10
+ #
11
+ # @example `EnforcedStyle: create_list`
12
+ # # bad
13
+ # 3.times { create :user }
14
+ #
15
+ # # good
16
+ # create_list :user, 3
17
+ #
18
+ # # good
19
+ # 3.times { |n| create :user, created_at: n.months.ago }
20
+ #
21
+ # @example `EnforcedStyle: n_times`
22
+ # # bad
23
+ # create_list :user, 3
24
+ #
25
+ # # good
26
+ # 3.times { create :user }
27
+ class CreateList < Cop
28
+ include ConfigurableEnforcedStyle
29
+
30
+ MSG_CREATE_LIST = 'Prefer create_list.'.freeze
31
+ MSG_N_TIMES = 'Prefer %<number>s.times.'.freeze
32
+
33
+ def_node_matcher :n_times_block?, <<-PATTERN
34
+ (block
35
+ (send (int _) :times)
36
+ ...
37
+ )
38
+ PATTERN
39
+
40
+ def_node_matcher :factory_call, <<-PATTERN
41
+ (send ${(const nil? {:FactoryGirl :FactoryBot}) nil?} :create (sym $_) $...)
42
+ PATTERN
43
+
44
+ def_node_matcher :factory_list_call, <<-PATTERN
45
+ (send ${(const nil? {:FactoryGirl :FactoryBot}) nil?} :create_list (sym $_) (int $_) $...)
46
+ PATTERN
47
+
48
+ def on_block(node)
49
+ return unless style == :create_list
50
+ return unless n_times_block?(node)
51
+ return unless contains_only_factory?(node.body)
52
+
53
+ add_offense(node.send_node,
54
+ location: :expression, message: MSG_CREATE_LIST)
55
+ end
56
+
57
+ def on_send(node)
58
+ return unless style == :n_times
59
+
60
+ factory_list_call(node) do |_receiver, _factory, count, _|
61
+ add_offense(
62
+ node,
63
+ location: :selector,
64
+ message: format(MSG_N_TIMES, number: count)
65
+ )
66
+ end
67
+ end
68
+
69
+ def autocorrect(node)
70
+ if style == :create_list
71
+ autocorrect_n_times_to_create_list(node)
72
+ else
73
+ autocorrect_create_list_to_n_times(node)
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def contains_only_factory?(node)
80
+ if node.block_type?
81
+ factory_call(node.send_node)
82
+ else
83
+ factory_call(node)
84
+ end
85
+ end
86
+
87
+ def autocorrect_n_times_to_create_list(node)
88
+ block = node.parent
89
+ count = block.receiver.source
90
+ replacement = factory_call_replacement(block.body, count)
91
+
92
+ lambda do |corrector|
93
+ corrector.replace(block.loc.expression, replacement)
94
+ end
95
+ end
96
+
97
+ def autocorrect_create_list_to_n_times(node)
98
+ replacement = generate_n_times_block(node)
99
+ lambda do |corrector|
100
+ corrector.replace(node.loc.expression, replacement)
101
+ end
102
+ end
103
+
104
+ def generate_n_times_block(node)
105
+ receiver, factory, count, options = *factory_list_call(node)
106
+
107
+ arguments = ":#{factory}"
108
+ options = build_options_string(options)
109
+ arguments += ", #{options}" unless options.empty?
110
+
111
+ replacement = format_receiver(receiver)
112
+ replacement += format_method_call(node, 'create', arguments)
113
+ "#{count}.times { #{replacement} }"
114
+ end
115
+
116
+ def factory_call_replacement(body, count)
117
+ receiver, factory, options = *factory_call(body)
118
+
119
+ arguments = ":#{factory}, #{count}"
120
+ options = build_options_string(options)
121
+ arguments += ", #{options}" unless options.empty?
122
+
123
+ replacement = format_receiver(receiver)
124
+ replacement += format_method_call(body, 'create_list', arguments)
125
+ replacement
126
+ end
127
+
128
+ def build_options_string(options)
129
+ options.map(&:source).join(', ')
130
+ end
131
+
132
+ def format_method_call(node, method, arguments)
133
+ if node.parenthesized?
134
+ "#{method}(#{arguments})"
135
+ else
136
+ "#{method} #{arguments}"
137
+ end
138
+ end
139
+
140
+ def format_receiver(receiver)
141
+ return '' unless receiver
142
+ "#{receiver.source}."
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end