rubocop-rspec 2.14.1 → 2.15.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
  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