rubocop-rspec 2.14.1 → 2.15.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 740118ee4ac02188e4bc7a4f62e03b31a6dda33ab3017377d954718cb7b356b1
4
- data.tar.gz: 7f17bb68eb575fb53dd8f0d2662afff8057bde7faeb19b6d94b0c3f1cb62f73d
3
+ metadata.gz: 1e120f8d06ef760cbe4f3a3d077ca6cd8ceeb274a26042a6e1469fe9a280c969
4
+ data.tar.gz: 0c80cc155e8f64b03a97de1327e454d9ff2efb1a75b02e3b098f868afed30d17
5
5
  SHA512:
6
- metadata.gz: 18fa9f2be3305f4e6388756e5223ccabdd85e951534f64e881c31a96d77a37f435c06197cd39a7808fb1690f56825ba442386668cfabe4335e784879f57d6ef2
7
- data.tar.gz: 1dd7abd8c281bb4c7ad6dd41bdb56c2de9be9e322935ae14d820863e7a64e2b39ad5f31e9603a88b77d52fb0ead146bae0c96297b2487831c2e263fe029244ec
6
+ metadata.gz: e982f97a23de2473c5cae6cd20bb653bcbcee806abddf0dda09c7b30692193858fc105c7b46ce3c004f28a7f680d1b3a475298c7b10d0e1499d5b80333fde504
7
+ data.tar.gz: c3b79b39a2c98d9103cb4edbf6c2d8e2cc957790fd8fa617e1fdece172f3dfaaf652bf482e7236a1da663f3b9afae943c17efee1e4a5f0293acc771bbc1c3c43
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## Master (Unreleased)
4
4
 
5
+ ## 2.15.0 (2022-11-03)
6
+
7
+ - Fix a false positive for `RSpec/RepeatedDescription` when different its block expectations are used. ([@ydah])
8
+ - Add `named_only` style to `RSpec/NamedSubject`. ([@kuahyeow])
9
+ - Fix `RSpec/FactoryBot/ConsistentParenthesesStyle` to ignore calls without the first positional argument. ([@pirj])
10
+ - Fix `RSpec/FactoryBot/ConsistentParenthesesStyle` to ignore calls inside a Hash or an Array. ([@pirj])
11
+ - Fix `RSpec/NestedGroups` to correctly use `AllowedGroups` config. ([@samrjenkins])
12
+ - Remove `Runners` and `HookScopes` RSpec DSL elements from configuration. ([@pirj])
13
+ - Add `with default RSpec/Language config` helper to `lib` (under `rubocop/rspec/shared_contexts/default_rspec_language_config_context`), to allow use for downstream cops based on `RuboCop::Cop::RSpec::Base`. ([@smcgivern])
14
+
15
+ ## 2.14.2 (2022-10-25)
16
+
17
+ - Fix an incorrect autocorrect for `FactoryBot/ConsistentParenthesesStyle` with `omit_parentheses` option when method name and first argument are not on same line. ([@ydah])
18
+ - Fix autocorrection loop in `RSpec/ExampleWording` for insufficient example wording. ([@pirj])
19
+ - Fix `RSpec/SortMetadata` not to reorder arguments of `include_`/`it_behaves_like`. ([@pirj])
20
+ - Fix a false positive for `RSpec/NoExpectationExample` when allowed pattern methods with arguments. ([@ydah])
21
+ - Change `RSpec/FilePath` so that it only checks suffix when path is under spec/routing or type is defined as routing. ([@r7kamura])
22
+
5
23
  ## 2.14.1 (2022-10-24)
6
24
 
7
25
  - Fix an error for `RSpec/Rails/InferredSpecType` with redundant type before other Hash metadata. ([@ydah])
@@ -716,6 +734,7 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
716
734
  [@jtannas]: https://github.com/jtannas
717
735
  [@kellysutton]: https://github.com/kellysutton
718
736
  [@koic]: https://github.com/koic
737
+ [@kuahyeow]: https://github.com/kuahyeow
719
738
  [@lazycoder9]: https://github.com/lazycoder9
720
739
  [@leoarnold]: https://github.com/leoarnold
721
740
  [@liberatys]: https://github.com/Liberatys
@@ -752,9 +771,11 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
752
771
  [@rrosenblum]: https://github.com/rrosenblum
753
772
  [@rspeicher]: https://github.com/rspeicher
754
773
  [@rst-j]: https://github.com/RST-J
774
+ [@samrjenkins]: https://github.com/samrjenkins
755
775
  [@schmijos]: https://github.com/schmijos
756
776
  [@seanpdoyle]: https://github.com/seanpdoyle
757
777
  [@sl4vr]: https://github.com/sl4vr
778
+ [@smcgivern]: https://github.com/smcgivern
758
779
  [@stephannv]: https://github.com/stephannv
759
780
  [@t3h2mas]: https://github.com/t3h2mas
760
781
  [@tdeo]: https://github.com/tdeo
data/config/default.yml CHANGED
@@ -12,8 +12,6 @@ RSpec:
12
12
  - Expectations
13
13
  - Helpers
14
14
  - Hooks
15
- - HookScopes
16
- - Runners
17
15
  - Subjects
18
16
  ExampleGroups:
19
17
  inherit_mode:
@@ -81,12 +79,6 @@ RSpec:
81
79
  - prepend_after
82
80
  - after
83
81
  - append_after
84
- HookScopes:
85
- - each
86
- - example
87
- - context
88
- - all
89
- - suite
90
82
  Includes:
91
83
  inherit_mode:
92
84
  merge:
@@ -98,10 +90,6 @@ RSpec:
98
90
  - include_examples
99
91
  Context:
100
92
  - include_context
101
- Runners:
102
- - to
103
- - to_not
104
- - not_to
105
93
  SharedGroups:
106
94
  inherit_mode:
107
95
  merge:
@@ -624,8 +612,13 @@ RSpec/MultipleSubjects:
624
612
  RSpec/NamedSubject:
625
613
  Description: Checks for explicitly referenced test subjects.
626
614
  Enabled: true
615
+ EnforcedStyle: always
616
+ SupportedStyles:
617
+ - always
618
+ - named_only
627
619
  IgnoreSharedExamples: true
628
620
  VersionAdded: 1.5.3
621
+ VersionChanged: '2.15'
629
622
  StyleGuide: https://rspec.rubystyle.guide/#use-subject
630
623
  Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NamedSubject
631
624
 
@@ -68,20 +68,15 @@ module RuboCop
68
68
  add_wording_offense(description_node, MSG_SHOULD)
69
69
  elsif message.match?(IT_PREFIX)
70
70
  add_wording_offense(description_node, MSG_IT)
71
- else
72
- check_and_handle_insufficient_examples(description_node)
71
+ elsif insufficient_docstring?(description_node)
72
+ add_offense(docstring(description_node),
73
+ message: MSG_INSUFFICIENT_DESCRIPTION)
73
74
  end
74
75
  end
75
76
  end
76
77
 
77
78
  private
78
79
 
79
- def check_and_handle_insufficient_examples(description)
80
- if insufficient_examples.include?(preprocess(text(description)))
81
- add_wording_offense(description, MSG_INSUFFICIENT_DESCRIPTION)
82
- end
83
- end
84
-
85
80
  def add_wording_offense(node, message)
86
81
  docstring = docstring(node)
87
82
 
@@ -137,6 +132,10 @@ module RuboCop
137
132
  cop_config.fetch('IgnoredWords', [])
138
133
  end
139
134
 
135
+ def insufficient_docstring?(description_node)
136
+ insufficient_examples.include?(preprocess(text(description_node)))
137
+ end
138
+
140
139
  def insufficient_examples
141
140
  examples = cop_config.fetch('DisallowedExamples', [])
142
141
  examples.map! { |example| preprocess(example) }
@@ -30,6 +30,16 @@ module RuboCop
30
30
  # create :login
31
31
  # create :login
32
32
  #
33
+ # # also good
34
+ # # when method name and first argument are not on same line
35
+ # create(
36
+ # :user
37
+ # )
38
+ # build(
39
+ # :user,
40
+ # name: 'foo'
41
+ # )
42
+ #
33
43
  class ConsistentParenthesesStyle < Base
34
44
  extend AutoCorrector
35
45
  include ConfigurableEnforcedStyle
@@ -45,15 +55,18 @@ module RuboCop
45
55
 
46
56
  FACTORY_CALLS = RuboCop::RSpec::FactoryBot::Language::METHODS
47
57
 
58
+ RESTRICT_ON_SEND = FACTORY_CALLS
59
+
48
60
  # @!method factory_call(node)
49
61
  def_node_matcher :factory_call, <<-PATTERN
50
- (send
51
- ${#factory_bot? nil?} %FACTORY_CALLS
52
- $...)
62
+ (send
63
+ {#factory_bot? nil?} %FACTORY_CALLS
64
+ {sym str send lvar} _*
65
+ )
53
66
  PATTERN
54
67
 
55
68
  def on_send(node)
56
- return if nested_call?(node) # prevent from nested matching
69
+ return if ambiguous_without_parentheses?(node)
57
70
 
58
71
  factory_call(node) do
59
72
  if node.parenthesized?
@@ -66,6 +79,7 @@ module RuboCop
66
79
 
67
80
  def process_with_parentheses(node)
68
81
  return unless style == :omit_parentheses
82
+ return unless same_line?(node, node.first_argument)
69
83
 
70
84
  add_offense(node.loc.selector,
71
85
  message: MSG_OMIT_PARENS) do |corrector|
@@ -82,8 +96,10 @@ module RuboCop
82
96
  end
83
97
  end
84
98
 
85
- def nested_call?(node)
86
- node.parent&.send_type?
99
+ def ambiguous_without_parentheses?(node)
100
+ node.parent&.send_type? ||
101
+ node.parent&.pair_type? ||
102
+ node.parent&.array_type?
87
103
  end
88
104
 
89
105
  private
@@ -76,8 +76,6 @@ module RuboCop
76
76
  return unless top_level_groups.one?
77
77
 
78
78
  example_group(node) do |send_node, example_group, arguments|
79
- next if routing_spec?(arguments)
80
-
81
79
  ensure_correct_file_path(send_node, example_group, arguments)
82
80
  end
83
81
  end
@@ -85,7 +83,7 @@ module RuboCop
85
83
  private
86
84
 
87
85
  def ensure_correct_file_path(send_node, example_group, arguments)
88
- pattern = pattern_for(example_group, arguments.first)
86
+ pattern = pattern_for(example_group, arguments)
89
87
  return if filename_ends_with?(pattern)
90
88
 
91
89
  # For the suffix shown in the offense message, modify the regular
@@ -97,11 +95,13 @@ module RuboCop
97
95
  end
98
96
 
99
97
  def routing_spec?(args)
100
- args.any?(&method(:routing_metadata?))
98
+ args.any?(&method(:routing_metadata?)) || routing_spec_path?
101
99
  end
102
100
 
103
- def pattern_for(example_group, method_name)
104
- if spec_suffix_only? || !example_group.const_type?
101
+ def pattern_for(example_group, arguments)
102
+ method_name = arguments.first
103
+ if spec_suffix_only? || !example_group.const_type? ||
104
+ routing_spec?(arguments)
105
105
  return pattern_for_spec_suffix_only
106
106
  end
107
107
 
@@ -149,8 +149,7 @@ module RuboCop
149
149
  end
150
150
 
151
151
  def filename_ends_with?(pattern)
152
- filename = File.expand_path(processed_source.buffer.name)
153
- filename.match?("#{pattern}$")
152
+ expanded_file_path.match?("#{pattern}$")
154
153
  end
155
154
 
156
155
  def relevant_rubocop_rspec_file?(_file)
@@ -160,6 +159,14 @@ module RuboCop
160
159
  def spec_suffix_only?
161
160
  cop_config['SpecSuffixOnly']
162
161
  end
162
+
163
+ def routing_spec_path?
164
+ expanded_file_path.include?('spec/routing/')
165
+ end
166
+
167
+ def expanded_file_path
168
+ File.expand_path(processed_source.buffer.name)
169
+ end
163
170
  end
164
171
  end
165
172
  end
@@ -12,11 +12,11 @@ module RuboCop
12
12
  # should be the most important object in your tests so they deserve
13
13
  # a descriptive name.
14
14
  #
15
- # This cop can be configured in your configuration using the
16
- # `IgnoreSharedExamples` which will not report offenses for implicit
15
+ # This cop can be configured in your configuration using `EnforcedStyle`,
16
+ # and `IgnoreSharedExamples` which will not report offenses for implicit
17
17
  # subjects in shared example groups.
18
18
  #
19
- # @example
19
+ # @example `EnforcedStyle: always` (default)
20
20
  # # bad
21
21
  # RSpec.describe User do
22
22
  # subject { described_class.new }
@@ -27,7 +27,7 @@ module RuboCop
27
27
  # end
28
28
  #
29
29
  # # good
30
- # RSpec.describe Foo do
30
+ # RSpec.describe User do
31
31
  # subject(:user) { described_class.new }
32
32
  #
33
33
  # it 'is valid' do
@@ -36,13 +36,49 @@ module RuboCop
36
36
  # end
37
37
  #
38
38
  # # also good
39
- # RSpec.describe Foo do
39
+ # RSpec.describe User do
40
+ # subject(:user) { described_class.new }
41
+ #
42
+ # it { is_expected.to be_valid }
43
+ # end
44
+ #
45
+ # @example `EnforcedStyle: named_only`
46
+ # # bad
47
+ # RSpec.describe User do
48
+ # subject(:user) { described_class.new }
49
+ #
50
+ # it 'is valid' do
51
+ # expect(subject.valid?).to be(true)
52
+ # end
53
+ # end
54
+ #
55
+ # # good
56
+ # RSpec.describe User do
40
57
  # subject(:user) { described_class.new }
41
58
  #
59
+ # it 'is valid' do
60
+ # expect(user.valid?).to be(true)
61
+ # end
62
+ # end
63
+ #
64
+ # # also good
65
+ # RSpec.describe User do
66
+ # subject { described_class.new }
67
+ #
42
68
  # it { is_expected.to be_valid }
43
69
  # end
44
70
  #
71
+ # # acceptable
72
+ # RSpec.describe User do
73
+ # subject { described_class.new }
74
+ #
75
+ # it 'is valid' do
76
+ # expect(subject.valid?).to be(true)
77
+ # end
78
+ # end
45
79
  class NamedSubject < Base
80
+ include ConfigurableEnforcedStyle
81
+
46
82
  MSG = 'Name your test subject if you need to reference it explicitly.'
47
83
 
48
84
  # @!method example_or_hook_block?(node)
@@ -62,14 +98,53 @@ module RuboCop
62
98
  end
63
99
 
64
100
  subject_usage(node) do |subject_node|
65
- add_offense(subject_node.loc.selector)
101
+ check_explicit_subject(subject_node)
66
102
  end
67
103
  end
68
104
 
105
+ private
106
+
69
107
  def ignored_shared_example?(node)
70
108
  cop_config['IgnoreSharedExamples'] &&
71
109
  node.each_ancestor(:block).any?(&method(:shared_example?))
72
110
  end
111
+
112
+ def check_explicit_subject(node)
113
+ return if allow_explicit_subject?(node)
114
+
115
+ add_offense(node.loc.selector)
116
+ end
117
+
118
+ def allow_explicit_subject?(node)
119
+ !always? && !named_only?(node)
120
+ end
121
+
122
+ def always?
123
+ style == :always
124
+ end
125
+
126
+ def named_only?(node)
127
+ style == :named_only &&
128
+ subject_definition_is_named?(node)
129
+ end
130
+
131
+ def subject_definition_is_named?(node)
132
+ subject = nearest_subject(node)
133
+
134
+ subject&.send_node&.arguments?
135
+ end
136
+
137
+ def nearest_subject(node)
138
+ node
139
+ .each_ancestor(:block)
140
+ .lazy
141
+ .map { |block_node| find_subject(block_node) }
142
+ .find(&:itself)
143
+ end
144
+
145
+ def find_subject(block_node)
146
+ block_node.body.child_nodes.find { |send_node| subject?(send_node) }
147
+ end
73
148
  end
74
149
  end
75
150
  end
@@ -133,7 +133,7 @@ module RuboCop
133
133
  def count_up_nesting?(node, example_group)
134
134
  example_group &&
135
135
  (node.block_type? &&
136
- !allowed_groups.include?(node.method_name))
136
+ !allowed_groups.include?(node.method_name.to_s))
137
137
  end
138
138
 
139
139
  def message(nesting)
@@ -77,7 +77,7 @@ module RuboCop
77
77
  def_node_search :includes_expectation?, <<~PATTERN
78
78
  {
79
79
  #{send_pattern('#Expectations.all')}
80
- (send nil? `#matches_allowed_pattern?)
80
+ (send nil? `#matches_allowed_pattern? ...)
81
81
  }
82
82
  PATTERN
83
83
 
@@ -45,8 +45,12 @@ module RuboCop
45
45
  def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
46
46
  return unless example_group?(node)
47
47
 
48
- repeated_descriptions(node).each do |repeated_description|
49
- add_offense(repeated_description)
48
+ repeated_descriptions(node).each do |description|
49
+ add_offense(description)
50
+ end
51
+
52
+ repeated_its(node).each do |its|
53
+ add_offense(its)
50
54
  end
51
55
  end
52
56
 
@@ -57,6 +61,7 @@ module RuboCop
57
61
  grouped_examples =
58
62
  RuboCop::RSpec::ExampleGroup.new(node)
59
63
  .examples
64
+ .reject { |n| n.definition.method?(:its) }
60
65
  .group_by { |example| example_signature(example) }
61
66
 
62
67
  grouped_examples
@@ -66,9 +71,27 @@ module RuboCop
66
71
  .map(&:definition)
67
72
  end
68
73
 
74
+ def repeated_its(node)
75
+ grouped_its =
76
+ RuboCop::RSpec::ExampleGroup.new(node)
77
+ .examples
78
+ .select { |n| n.definition.method?(:its) }
79
+ .group_by { |example| its_signature(example) }
80
+
81
+ grouped_its
82
+ .select { |signatures, group| signatures.any? && group.size > 1 }
83
+ .values
84
+ .flatten
85
+ .map(&:to_node)
86
+ end
87
+
69
88
  def example_signature(example)
70
89
  [example.metadata, example.doc_string]
71
90
  end
91
+
92
+ def its_signature(example)
93
+ [example.doc_string, example]
94
+ end
72
95
  end
73
96
  end
74
97
  end
@@ -26,8 +26,7 @@ module RuboCop
26
26
  def_node_matcher :rspec_metadata, <<~PATTERN
27
27
  (block
28
28
  (send
29
- #rspec? {#Examples.all #ExampleGroups.all #SharedGroups.all #Hooks.all #Includes.all}
30
- _ ${send str sym}* (hash $...)?)
29
+ #rspec? {#Examples.all #ExampleGroups.all #SharedGroups.all #Hooks.all} _ ${send str sym}* (hash $...)?)
31
30
  ...)
32
31
  PATTERN
33
32
 
@@ -135,8 +135,9 @@ module RuboCop
135
135
  end
136
136
 
137
137
  module HookScopes # :nodoc:
138
+ ALL = %i[each example context all suite].freeze
138
139
  def self.all(element)
139
- Language.config['HookScopes'].include?(element.to_s)
140
+ ALL.include?(element)
140
141
  end
141
142
  end
142
143
 
@@ -158,8 +159,9 @@ module RuboCop
158
159
  end
159
160
 
160
161
  module Runners # :nodoc:
162
+ ALL = %i[to to_not not_to].freeze
161
163
  def self.all(element)
162
- Language.config['Runners'].include?(element.to_s)
164
+ ALL.include?(element)
163
165
  end
164
166
  end
165
167
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_context 'with default RSpec/Language config' do
4
+ include_context 'config'
5
+
6
+ # Deep duplication is needed to prevent config leakage between examples
7
+ let(:other_cops) do
8
+ default_language = RuboCop::ConfigLoader
9
+ .default_configuration['RSpec']['Language']
10
+ default_include = RuboCop::ConfigLoader
11
+ .default_configuration['RSpec']['Include']
12
+ { 'RSpec' =>
13
+ {
14
+ 'Include' => default_include,
15
+ 'Language' => deep_dup(default_language)
16
+ } }
17
+ end
18
+
19
+ def deep_dup(object)
20
+ case object
21
+ when Array
22
+ object.map { |item| deep_dup(item) }
23
+ when Hash
24
+ object.transform_values(&method(:deep_dup))
25
+ else
26
+ object # only collections undergo modifications and need duping
27
+ end
28
+ end
29
+ end
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module RSpec
5
5
  # Version information for the RSpec RuboCop plugin.
6
6
  module Version
7
- STRING = '2.14.1'
7
+ STRING = '2.15.0'
8
8
  end
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.14.1
4
+ version: 2.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Backus
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-10-24 00:00:00.000000000 Z
13
+ date: 2022-11-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -178,6 +178,7 @@ files:
178
178
  - lib/rubocop/rspec/language.rb
179
179
  - lib/rubocop/rspec/language/node_pattern.rb
180
180
  - lib/rubocop/rspec/node.rb
181
+ - lib/rubocop/rspec/shared_contexts/default_rspec_language_config_context.rb
181
182
  - lib/rubocop/rspec/version.rb
182
183
  - lib/rubocop/rspec/wording.rb
183
184
  homepage: https://github.com/rubocop/rubocop-rspec