Skip to content

Commit 38be753

Browse files
authored
Merge pull request #1013 from rubocop-hq/add-v2-migration-docs
Add v2 update docs
2 parents 9a5d604 + 5a9714e commit 38be753

File tree

5 files changed

+346
-4
lines changed

5 files changed

+346
-4
lines changed

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* xref:installation.adoc[Installation]
33
* xref:usage.adoc[Usage]
44
* xref:cops.adoc[Cops]
5+
* xref:upgrade_to_version_2.adoc[Upgrade to 2.x]
56
* Cops Documentation
67
** xref:cops_capybara.adoc[Capybara]
78
** xref:cops_factorybot.adoc[FactoryBot]

docs/modules/ROOT/pages/index.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
RSpec-specific analysis for your projects, as an extension to
44
https://github.com/rubocop-hq/rubocop[RuboCop].
55

6+
RuboCop RSpec follows the https://docs.rubocop.org/rubocop/versioning.html[RuboCop versioning guide].
7+
In a nutshell, between major versions new cops are introduced in a special `pending` status.
8+
That means that they won’t be run unless explicitly told otherwise.
9+
RuboCop will warn on start that certain cops are neither explicitly enabled and disabled.
10+
On a major version release, all `pending` cops are enabled.
11+
612
== Project Goals
713

814
* Enforce the guidelines and best practices outlined in the community https://rspec.rubystyle.guide[RSpec style guide]
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
= Upgrade to Version 2.x
2+
:doctype: book
3+
4+
== Configuration File Update
5+
6+
In version 2.x:
7+
8+
- `RSpec/InvalidPredicateMatcher` cop is removed
9+
- `CustomIncludeMethods` configuration option for `RSpec/EmptyExampleGroup` is removed
10+
- cop departments are nested for cops with a department that doesn’t match the extension name (`Capybara`, `FactoryBot`, `Rails`)
11+
- `AllCops/RSpec/Patterns`/`AllCops/FactoryBot/Patterns` options are removed
12+
- Calling `super` from `#on_new_investigation` defined in a cop is mandatory now
13+
- In specs, do not define `cop`
14+
15+
[discrete]
16+
=== Adjust the configuration of `RSpec/EmptyExampleGroup`
17+
18+
[source,yaml]
19+
----
20+
# .rubocop.yml
21+
22+
# Before
23+
RSpec/EmptyExampleGroup:
24+
CustomIncludeMethods:
25+
- include_tests
26+
27+
# After
28+
RSpec:
29+
Language:
30+
Includes:
31+
Examples:
32+
- include_tests
33+
----
34+
35+
=== Add a top-level `RSpec` department
36+
37+
RuboCop extensions had cops with clashing names and departments, e.g. both `rspec-rails` and `rubocop-rspec` had `Rails::HttpStatus` cops.
38+
To avoid issues, e.g. inability to disable just one of the cops, each extension now has its own uber-department.
39+
Expectedly, RuboCop RSpec’s uber-department name is `RSpec`.
40+
Changes are only applied to cops that don’t already have the department set to `RSpec`, i.e. `Capybara`, `FactoryBot` and `Rails`.
41+
42+
[source,yaml]
43+
----
44+
# .rubocop.yml
45+
46+
# Before
47+
Capybara/CurrentPathExpectation:
48+
Enabled: false
49+
50+
FactoryBot/AttributeDefinedStatically:
51+
Enabled: false
52+
53+
# remains the same
54+
RSpec/EmptyExampleGroup:
55+
Enabled: false
56+
57+
# After
58+
RSpec/Capybara/CurrentPathExpectation:
59+
Enabled: false
60+
61+
RSpec/FactoryBot/AttributeDefinedStatically:
62+
Enabled: false
63+
64+
# remains the same
65+
RSpec/EmptyExampleGroup:
66+
Enabled: false
67+
----
68+
69+
https://github.com/rubocop-hq/rubocop/pull/8490[Learn more about this change].
70+
71+
72+
=== Use the RuboCop standard `Include` option to filter inspected files
73+
74+
`Patterns` was a RuboCop RSpec-specific option, and RuboCop has a standard replacement.
75+
76+
[source,yaml]
77+
----
78+
# .rubocop.yml
79+
80+
# Before
81+
AllCops:
82+
RSpec/FactoryBot:
83+
Patterns:
84+
- spec/factories/**/*.rb
85+
- property/factories/**/*.rb
86+
87+
# After
88+
RSpec/FactoryBot:
89+
Include:
90+
- spec/factories/**/*.rb
91+
- property/factories/**/*.rb
92+
----
93+
94+
NOTE: Please keep in mind that `Include`'s merge mode is set to override the default settings, so if you intend to add a path while keeping the default paths, you should include the default `Include` paths in your configuration.
95+
96+
https://github.com/rubocop-hq/rubocop-rspec/pull/1063[Learn more about this change].
97+
98+
== Custom Cop Update Guide
99+
100+
Due to significant API changes, custom cops may break.
101+
Here is the summary of the changes:
102+
103+
1. The base class for cops is now `RuboCop::Cop::RSpec::Base` instead of `RuboCop::Cop::RSpec::Cop`.
104+
105+
2. The module `RuboCop::Cop::RSpec::TopLevelDescribe` is replaced with a more generic `RuboCop::Cop::RSpec::TopLevelGroup`.
106+
107+
3. `RuboCop::RSpec::Language` has been completely rewritten to support dynamic RSpec DSL aliases and negated matchers to fully support third-party libraries such as RSpec Rails, Pundit, Action Policy and many others.
108+
109+
4. RuboCop RSpec updated the dependency of RuboCop to 1.0+.
110+
111+
Below are the necessary steps to update custom cops to work with `rubocop-rspec` version 2.x.
112+
113+
114+
=== Change the Parent Class
115+
116+
Change the parent class of the custom cops from `RuboCop::Cop::RSpec::Cop` to `RuboCop::Cop::RSpec::Base`.
117+
118+
[source,ruby]
119+
----
120+
# Before
121+
module RuboCop
122+
module Cop
123+
module RSpec
124+
class FightPowerty < Cop
125+
126+
# After
127+
module RuboCop
128+
module Cop
129+
module RSpec
130+
class FightPowerty < Base
131+
----
132+
133+
https://github.com/rubocop-hq/rubocop-rspec/pull/962[Example pull request].
134+
135+
136+
=== Replace `TopLevelDescribe`
137+
138+
`TopLevelDescribe` was incomplete, had poor performance and did not distinguish between example groups and shared example groups.
139+
140+
`TopLevelGroup` provides a similar interface, but instead of a single `on_top_level_describe` hook there are two, `on_top_level_example_group` and `on_top_level_group`.
141+
There’s no need yet for `on_top_level_shared_group` for RuboCop core cops, but if your custom cop needs such a hook, please feel free to send a pull request.
142+
143+
Additionally, `single_top_level_describe?` is removed with no direct replacement.
144+
You may use `top_level_groups` query method instead, e.g. `top_level_groups.one?`.
145+
146+
Example pull requests to replace `TopLevelDescribe` with `TopLevelGroup` [https://github.com/rubocop-hq/rubocop-rspec/pull/978[1], https://github.com/rubocop-hq/rubocop-rspec/pull/932[2], https://github.com/rubocop-hq/rubocop-rspec/pull/977[3]].
147+
148+
149+
=== Change the `Language` Module Usages
150+
151+
To allow for lazy initialization, and for loading of the language configuration after the class are loaded, a https://docs.rubocop.org/rubocop-ast/node_pattern.html#to-call-functions[function call feature of RuboCop AST] is used.
152+
153+
The `RuboCop::RSpec::Language` is completely different now.
154+
155+
`Hooks::ALL` and alike, and their accompanying helpers work differently.
156+
157+
[source,ruby]
158+
----
159+
# Before
160+
def_node_matcher :shared_context,
161+
SharedGroups::CONTEXT.block_pattern
162+
163+
# After
164+
def_node_matcher :shared_context,
165+
block_pattern('#SharedGroups.context')
166+
----
167+
168+
[source,ruby]
169+
----
170+
# Before
171+
def_node_search :examples?,
172+
(Includes::EXAMPLES + Examples::ALL).send_pattern
173+
174+
# After
175+
def_node_search :examples?,
176+
send_pattern('{#Includes.examples #Examples.all}')
177+
----
178+
179+
[source,ruby]
180+
----
181+
# Before
182+
def_node_search :find_rspec_blocks,
183+
ExampleGroups::ALL.block_pattern
184+
185+
# After
186+
def_node_search :find_rspec_blocks,
187+
block_pattern('#ExampleGroups.all')
188+
----
189+
190+
If you were calling Language elements directly, you have to make the same adjustments:
191+
192+
[source,ruby]
193+
----
194+
# Before
195+
node&.sym_type? && Hooks::Scopes::ALL.include?(node.value)
196+
197+
# After
198+
node&.sym_type? && Language::HookScopes.all(node.value)
199+
----
200+
201+
You may see a common pattern in the change.
202+
There is a small exception, though:
203+
204+
[source,ruby]
205+
----
206+
# Before
207+
ExampleGroups::GROUPS
208+
209+
# After
210+
ExampleGroups.regular
211+
212+
# Before
213+
Examples::EXAMPLES
214+
215+
# After
216+
Examples.regular
217+
----
218+
219+
https://github.com/rubocop-hq/rubocop-rspec/pull/956[Pull request with more examples].
220+
221+
=== Always call `super` from `on_new_investigation` in your cops
222+
223+
`on_new_investigation` is now used for internal purposes, and not calling `super` from your cop involves a risk of configuration not being properly loaded, and dynamic RSpec DSL matchers won't work.
224+
225+
NOTE: You don't have to define `on_new_investigation` in your cops unless you need to.
226+
227+
[source,ruby]
228+
----
229+
module RuboCop
230+
module Cop
231+
module RSpec
232+
class MultipleMemoizedHelpers < Base
233+
def on_new_investigation
234+
super # Always call `super`
235+
@example_group_memoized_helpers = {}
236+
end
237+
end
238+
end
239+
end
240+
end
241+
----
242+
243+
https://github.com/rubocop-hq/rubocop-rspec/pull/956[Pull request with more examples].
244+
245+
=== Use `:config` RSpec metadata in cop specs
246+
247+
`:config` metadata should be added to the top-level example group of your cop spec.
248+
Doing otherwise will not pass configuration to the cop, and dynamic RSpec DSL matchers might not work.
249+
250+
[source,ruby]
251+
----
252+
# Before
253+
RSpec.describe 'MyMightyCop' do
254+
let(:cop) { described_class.new }
255+
# ...
256+
end
257+
258+
# After
259+
RSpec.describe 'MyMightyCop', :config do
260+
# `cop` is defined for you by RuboCop's shared context that is included
261+
# to example groups with :config metadata
262+
263+
# ...
264+
end
265+
----
266+
267+
https://github.com/rubocop-hq/rubocop/blob/51ff1d7e29c985732fe129082c98d66c531a2611/lib/rubocop/rspec/shared_contexts.rb#L56[RuboCop takes care of defining everything for your cop specs].
268+
269+
=== Conform with RuboCop API Changes
270+
271+
The parent project, RuboCop, has API changes.
272+
While they won’t result in cop breakages, it is recommended to update cops to use new API’s.
273+
Follow the https://docs.rubocop.org/rubocop/v1_upgrade_notes[RuboCop v1 update guide] to adjust custom cops’ use of RuboCop’s auto-correction API.

docs/modules/ROOT/pages/usage.adoc

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,79 @@
11
= Usage
22

3-
You need to tell RuboCop to load the RSpec extension. There are three
4-
ways to do this:
3+
You need to tell RuboCop to load the RSpec extension.
4+
There are three ways to do this:
55

66
== RuboCop configuration file
77

8-
Put this into your `.rubocop.yml`.
8+
Put this into your `.rubocop.yml`:
99

1010
----
1111
require: rubocop-rspec
1212
----
1313

14+
or, if you are using several extensions:
15+
16+
----
17+
require:
18+
- rubocop-rspec
19+
- rubocop-performance
20+
----
21+
1422
Now you can run `rubocop` and it will automatically load the RuboCop RSpec
1523
cops together with the standard cops.
1624

25+
=== RSpec DSL configuration
26+
27+
In case you https://github.com/rspec/rspec-core/blob/b0d0843a285693c64cdbe0c85726db155b46047e/lib/rspec/core/configuration.rb#L1122[define aliases for RSpec DSL], i.e. examples, example groups, hooks, or include example statements, you need to configure it so those elements are properly detected by RuboCop RSpec.
28+
29+
[source,ruby]
30+
----
31+
# spec/spec_helper.rb
32+
RSpec.configure do |c|
33+
c.alias_example_group_to :detail, :detailed => true
34+
end
35+
36+
# spec/detail_spec.rb
37+
RSpec.detail "a detail" do
38+
it "can do some less important stuff" do
39+
end
40+
end
41+
----
42+
43+
[source,yaml]
44+
----
45+
# .rubocop.yml
46+
RSpec:
47+
Language:
48+
ExampleGroups:
49+
Regular:
50+
- detail
51+
----
52+
53+
Some libraries extensively define RSpec DSL aliases (e.g. Pundit, Action Policy) or augment existing elements providing the same semantics (e.g. `let_it_be` from `test-prof`).
54+
Those libraries can provide necessary configuration, but won't necessarily do so.
55+
If they do, their README will mention that you have to explicitly require their configuration from your `.rubocop.yml` file.
56+
57+
[source,yaml]
58+
----
59+
# .rubocop.yml
60+
61+
require:
62+
- rubocop-rspec
63+
- test-prof
64+
65+
# or
66+
67+
RSpec:
68+
Language:
69+
Helpers:
70+
- let_it_be
71+
----
72+
73+
NOTE: the default merge mode is to inherit, so you won't remove any of the default settings.
74+
75+
RuboCop RSpec's https://github.com/rubocop-hq/rubocop-rspec/blob/a43424527c09fae2e6ddb133f4b2988f6c46bb2e/config/default.yml#L6[default configuration] is a good source of information on what can be configured.
76+
1777
== Command line
1878

1979
[source,bash]
@@ -53,3 +113,5 @@ RSpec:
53113
Include:
54114
- '**/*_test.rb'
55115
----
116+
117+
NOTE: Please keep in mind that `Include`'s merge mode is set to override the default settings, so if you intend to add a path while keeping the default paths, you should include the default `Include` paths in your configuration.

spec/shared/default_rspec_language_config_context.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
RSpec.shared_context 'with default AllCops.RSpec.Language config', :config do
3+
RSpec.shared_context 'with default RSpec/Language config', :config do
44
# Deep duplication is needed to prevent config leakage between examples
55
let(:other_cops) do
66
default_language = RuboCop::ConfigLoader

0 commit comments

Comments
 (0)