rubocop-rspec 1.24.0 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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