Skip to content

Commit b8dd1bb

Browse files
authored
Merge pull request #994 from biinari/feature/repeated_include_examples
Add cop RSpec/RepeatedIncludeExample
2 parents 46b39c7 + 8d9e137 commit b8dd1bb

File tree

7 files changed

+402
-0
lines changed

7 files changed

+402
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Master (Unreleased)
44

5+
* Add `RSpec/RepeatedIncludeExample` cop. ([@biinari][])
6+
57
## 1.43.1 (2020-08-17)
68

79
* Fix `RSpec/FilePath` when checking a file defining e.g. an empty class. ([@bquorning][])
@@ -550,3 +552,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
550552
[@mlarraz]: https://github.com/mlarraz
551553
[@jtannas]: https://github.com/jtannas
552554
[@mockdeep]: https://github.com/mockdeep
555+
[@biinari]: https://github.com/biinari

config/default.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,12 @@ RSpec/RepeatedExampleGroupDescription:
494494
VersionAdded: '1.38'
495495
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription
496496

497+
RSpec/RepeatedIncludeExample:
498+
Description: Check for repeated include of shared examples.
499+
Enabled: true
500+
VersionAdded: '1.44'
501+
StyleGuide: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedIncludeExample
502+
497503
RSpec/ReturnFromStub:
498504
Description: Checks for consistent style of stub's return setting.
499505
Enabled: true
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module RSpec
6+
# Check for repeated include of shared examples.
7+
#
8+
# @example
9+
#
10+
# # bad
11+
# describe 'foo' do
12+
# include_examples 'cool stuff'
13+
# include_examples 'cool stuff'
14+
# end
15+
#
16+
# # bad
17+
# describe 'foo' do
18+
# it_behaves_like 'a cool', 'thing'
19+
# it_behaves_like 'a cool', 'thing'
20+
# end
21+
#
22+
# # bad
23+
# context 'foo' do
24+
# it_should_behave_like 'a duck'
25+
# it_should_behave_like 'a duck'
26+
# end
27+
#
28+
# # good
29+
# describe 'foo' do
30+
# include_examples 'cool stuff'
31+
# end
32+
#
33+
# describe 'bar' do
34+
# include_examples 'cool stuff'
35+
# end
36+
#
37+
# # good
38+
# describe 'foo' do
39+
# it_behaves_like 'a cool', 'thing'
40+
# it_behaves_like 'a cool', 'person'
41+
# end
42+
#
43+
# # good
44+
# context 'foo' do
45+
# it_should_behave_like 'a duck'
46+
# it_should_behave_like 'a goose'
47+
# end
48+
#
49+
class RepeatedIncludeExample < Base
50+
MSG = 'Repeated include of shared_examples %<name>s ' \
51+
'on line(s) %<repeat>s'
52+
53+
def_node_matcher :several_include_examples?, <<-PATTERN
54+
(begin <#include_examples? #include_examples? ...>)
55+
PATTERN
56+
57+
def_node_matcher :include_examples?, Includes::EXAMPLES.send_pattern
58+
59+
def_node_matcher :shared_examples_name, <<-PATTERN
60+
(send _ #{Includes::EXAMPLES.node_pattern_union} $_ ...)
61+
PATTERN
62+
63+
def on_begin(node)
64+
return unless several_include_examples?(node)
65+
66+
repeated_include_examples(node).each do |item, repeats|
67+
add_offense(item, message: message(item, repeats))
68+
end
69+
end
70+
71+
private
72+
73+
def repeated_include_examples(node)
74+
node
75+
.children
76+
.select { |child| literal_include_examples?(child) }
77+
.group_by { |child| signature_keys(child) }
78+
.values
79+
.reject(&:one?)
80+
.flat_map { |items| add_repeated_lines(items) }
81+
end
82+
83+
def literal_include_examples?(node)
84+
include_examples?(node) &&
85+
node.arguments.all?(&:recursive_literal_or_const?)
86+
end
87+
88+
def add_repeated_lines(items)
89+
repeated_lines = items.map(&:first_line)
90+
items.map { |item| [item, repeated_lines - [item.first_line]] }
91+
end
92+
93+
def signature_keys(item)
94+
item.arguments
95+
end
96+
97+
def message(item, repeats)
98+
format(MSG, name: shared_examples_name(item).source, repeat: repeats)
99+
end
100+
end
101+
end
102+
end
103+
end

lib/rubocop/cop/rspec_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
require_relative 'rspec/repeated_example'
8080
require_relative 'rspec/repeated_example_group_body'
8181
require_relative 'rspec/repeated_example_group_description'
82+
require_relative 'rspec/repeated_include_example'
8283
require_relative 'rspec/return_from_stub'
8384
require_relative 'rspec/scattered_let'
8485
require_relative 'rspec/scattered_setup'

manual/cops.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
* [RSpec/RepeatedExample](cops_rspec.md#rspecrepeatedexample)
7979
* [RSpec/RepeatedExampleGroupBody](cops_rspec.md#rspecrepeatedexamplegroupbody)
8080
* [RSpec/RepeatedExampleGroupDescription](cops_rspec.md#rspecrepeatedexamplegroupdescription)
81+
* [RSpec/RepeatedIncludeExample](cops_rspec.md#rspecrepeatedincludeexample)
8182
* [RSpec/ReturnFromStub](cops_rspec.md#rspecreturnfromstub)
8283
* [RSpec/ScatteredLet](cops_rspec.md#rspecscatteredlet)
8384
* [RSpec/ScatteredSetup](cops_rspec.md#rspecscatteredsetup)

manual/cops_rspec.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2828,6 +2828,61 @@ end
28282828

28292829
* [https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription](https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedExampleGroupDescription)
28302830

2831+
## RSpec/RepeatedIncludeExample
2832+
2833+
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged
2834+
--- | --- | --- | --- | ---
2835+
Enabled | Yes | No | 1.44 | -
2836+
2837+
Check for repeated include of shared examples.
2838+
2839+
### Examples
2840+
2841+
```ruby
2842+
# bad
2843+
describe 'foo' do
2844+
include_examples 'cool stuff'
2845+
include_examples 'cool stuff'
2846+
end
2847+
2848+
# bad
2849+
describe 'foo' do
2850+
it_behaves_like 'a cool', 'thing'
2851+
it_behaves_like 'a cool', 'thing'
2852+
end
2853+
2854+
# bad
2855+
context 'foo' do
2856+
it_should_behave_like 'a duck'
2857+
it_should_behave_like 'a duck'
2858+
end
2859+
2860+
# good
2861+
describe 'foo' do
2862+
include_examples 'cool stuff'
2863+
end
2864+
2865+
describe 'bar' do
2866+
include_examples 'cool stuff'
2867+
end
2868+
2869+
# good
2870+
describe 'foo' do
2871+
it_behaves_like 'a cool', 'thing'
2872+
it_behaves_like 'a cool', 'person'
2873+
end
2874+
2875+
# good
2876+
context 'foo' do
2877+
it_should_behave_like 'a duck'
2878+
it_should_behave_like 'a goose'
2879+
end
2880+
```
2881+
2882+
### References
2883+
2884+
* [https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedIncludeExample](https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/RepeatedIncludeExample)
2885+
28312886
## RSpec/ReturnFromStub
28322887

28332888
Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

0 commit comments

Comments
 (0)