rubocop-rspec-guide 0.2.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +37 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +296 -0
- data/Rakefile +10 -0
- data/config/default.yml +38 -0
- data/devbox.json +14 -0
- data/devbox.lock +74 -0
- data/lib/rubocop/cop/factory_bot_guide/dynamic_attributes_for_time_and_random.rb +121 -0
- data/lib/rubocop/cop/rspec_guide/characteristics_and_contexts.rb +91 -0
- data/lib/rubocop/cop/rspec_guide/context_setup.rb +88 -0
- data/lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb +163 -0
- data/lib/rubocop/cop/rspec_guide/duplicate_let_values.rb +193 -0
- data/lib/rubocop/cop/rspec_guide/happy_path_first.rb +116 -0
- data/lib/rubocop/cop/rspec_guide/invariant_examples.rb +143 -0
- data/lib/rubocop/rspec/guide/version.rb +9 -0
- data/lib/rubocop/rspec/guide.rb +12 -0
- data/lib/rubocop-rspec-guide.rb +13 -0
- data/sig/rubocop/rspec/guide.rbs +8 -0
- metadata +136 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpecGuide
|
|
6
|
+
# Detects examples that repeat in all leaf contexts.
|
|
7
|
+
# These invariants should be extracted to shared_examples.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# # bad
|
|
11
|
+
# describe 'Validator' do
|
|
12
|
+
# context 'with valid data' do
|
|
13
|
+
# it 'responds to valid?' do
|
|
14
|
+
# expect(subject).to respond_to(:valid?)
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# context 'with invalid data' do
|
|
19
|
+
# it 'responds to valid?' do
|
|
20
|
+
# expect(subject).to respond_to(:valid?)
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# context 'with empty data' do
|
|
25
|
+
# it 'responds to valid?' do
|
|
26
|
+
# expect(subject).to respond_to(:valid?)
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# # good
|
|
32
|
+
# shared_examples 'a validator' do
|
|
33
|
+
# it 'responds to valid?' do
|
|
34
|
+
# expect(subject).to respond_to(:valid?)
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# describe 'Validator' do
|
|
39
|
+
# context 'with valid data' do
|
|
40
|
+
# it_behaves_like 'a validator'
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# context 'with invalid data' do
|
|
44
|
+
# it_behaves_like 'a validator'
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# context 'with empty data' do
|
|
48
|
+
# it_behaves_like 'a validator'
|
|
49
|
+
# end
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
class InvariantExamples < Base
|
|
53
|
+
MSG = "Example `%<description>s` repeats in all %<count>d leaf contexts. " \
|
|
54
|
+
"Consider extracting to shared_examples as an interface invariant."
|
|
55
|
+
|
|
56
|
+
# @!method example_with_description?(node)
|
|
57
|
+
def_node_matcher :example_with_description?, <<~PATTERN
|
|
58
|
+
(block
|
|
59
|
+
(send nil? {:it :specify :example} (str $_description))
|
|
60
|
+
...)
|
|
61
|
+
PATTERN
|
|
62
|
+
|
|
63
|
+
# @!method context_or_describe?(node)
|
|
64
|
+
def_node_matcher :context_or_describe?, <<~PATTERN
|
|
65
|
+
(block
|
|
66
|
+
(send nil? {:describe :context} ...)
|
|
67
|
+
...)
|
|
68
|
+
PATTERN
|
|
69
|
+
|
|
70
|
+
# @!method top_level_describe?(node)
|
|
71
|
+
def_node_matcher :top_level_describe?, <<~PATTERN
|
|
72
|
+
(block
|
|
73
|
+
(send nil? :describe ...)
|
|
74
|
+
...)
|
|
75
|
+
PATTERN
|
|
76
|
+
|
|
77
|
+
def on_block(node)
|
|
78
|
+
return unless top_level_describe?(node)
|
|
79
|
+
|
|
80
|
+
# Find all leaf contexts (contexts with no nested contexts)
|
|
81
|
+
leaf_contexts = find_leaf_contexts(node)
|
|
82
|
+
|
|
83
|
+
min_leaf_contexts = cop_config["MinLeafContexts"] || 3
|
|
84
|
+
return if leaf_contexts.size < min_leaf_contexts
|
|
85
|
+
|
|
86
|
+
# Collect example descriptions from each leaf
|
|
87
|
+
examples_by_leaf = leaf_contexts.map do |leaf|
|
|
88
|
+
collect_example_descriptions(leaf)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Find descriptions that appear in ALL leaves
|
|
92
|
+
common_descriptions = examples_by_leaf.reduce(:&)
|
|
93
|
+
return if common_descriptions.nil? || common_descriptions.empty?
|
|
94
|
+
|
|
95
|
+
# Add offenses for all examples with common descriptions
|
|
96
|
+
leaf_contexts.each do |leaf|
|
|
97
|
+
leaf.each_descendant(:block) do |example_node|
|
|
98
|
+
example_with_description?(example_node) do |description|
|
|
99
|
+
if common_descriptions.include?(description)
|
|
100
|
+
add_offense(
|
|
101
|
+
example_node,
|
|
102
|
+
message: format(MSG, description: description, count: leaf_contexts.size)
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def find_leaf_contexts(node)
|
|
113
|
+
leaves = []
|
|
114
|
+
|
|
115
|
+
node.each_descendant(:block) do |child|
|
|
116
|
+
next unless context_or_describe?(child)
|
|
117
|
+
|
|
118
|
+
# Check if this context has nested contexts
|
|
119
|
+
has_nested = child.each_descendant(:block).any? do |nested|
|
|
120
|
+
context_or_describe?(nested) && nested != child
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
leaves << child unless has_nested
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
leaves
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def collect_example_descriptions(context_node)
|
|
130
|
+
descriptions = []
|
|
131
|
+
|
|
132
|
+
context_node.each_descendant(:block) do |child|
|
|
133
|
+
example_with_description?(child) do |description|
|
|
134
|
+
descriptions << description
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
descriptions.uniq
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubocop"
|
|
4
|
+
require "rubocop-rspec"
|
|
5
|
+
|
|
6
|
+
require_relative "rubocop/rspec/guide/version"
|
|
7
|
+
require_relative "rubocop/cop/rspec_guide/characteristics_and_contexts"
|
|
8
|
+
require_relative "rubocop/cop/rspec_guide/duplicate_let_values"
|
|
9
|
+
require_relative "rubocop/cop/rspec_guide/duplicate_before_hooks"
|
|
10
|
+
require_relative "rubocop/cop/rspec_guide/invariant_examples"
|
|
11
|
+
require_relative "rubocop/cop/rspec_guide/happy_path_first"
|
|
12
|
+
require_relative "rubocop/cop/rspec_guide/context_setup"
|
|
13
|
+
require_relative "rubocop/cop/factory_bot_guide/dynamic_attributes_for_time_and_random"
|
metadata
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rubocop-rspec-guide
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alexey Matskevich
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rubocop
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.50'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.50'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rubocop-rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.20'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.20'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.12'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.12'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: standard
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.24'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.24'
|
|
82
|
+
description: A collection of custom RuboCop cops that enforce best practices from
|
|
83
|
+
the RSpec style guide, including context structure, testing patterns, and FactoryBot
|
|
84
|
+
usage.
|
|
85
|
+
email:
|
|
86
|
+
- github_job@mackevich.addymail.com
|
|
87
|
+
executables: []
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- ".rspec"
|
|
92
|
+
- ".rubocop.yml"
|
|
93
|
+
- ".standard.yml"
|
|
94
|
+
- CHANGELOG.md
|
|
95
|
+
- LICENSE.txt
|
|
96
|
+
- README.md
|
|
97
|
+
- Rakefile
|
|
98
|
+
- config/default.yml
|
|
99
|
+
- devbox.json
|
|
100
|
+
- devbox.lock
|
|
101
|
+
- lib/rubocop-rspec-guide.rb
|
|
102
|
+
- lib/rubocop/cop/factory_bot_guide/dynamic_attributes_for_time_and_random.rb
|
|
103
|
+
- lib/rubocop/cop/rspec_guide/characteristics_and_contexts.rb
|
|
104
|
+
- lib/rubocop/cop/rspec_guide/context_setup.rb
|
|
105
|
+
- lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb
|
|
106
|
+
- lib/rubocop/cop/rspec_guide/duplicate_let_values.rb
|
|
107
|
+
- lib/rubocop/cop/rspec_guide/happy_path_first.rb
|
|
108
|
+
- lib/rubocop/cop/rspec_guide/invariant_examples.rb
|
|
109
|
+
- lib/rubocop/rspec/guide.rb
|
|
110
|
+
- lib/rubocop/rspec/guide/version.rb
|
|
111
|
+
- sig/rubocop/rspec/guide.rbs
|
|
112
|
+
homepage: https://github.com/rspec-guide/rubocop-rspec-guide
|
|
113
|
+
licenses:
|
|
114
|
+
- MIT
|
|
115
|
+
metadata:
|
|
116
|
+
homepage_uri: https://github.com/rspec-guide/rubocop-rspec-guide
|
|
117
|
+
source_code_uri: https://github.com/rspec-guide/rubocop-rspec-guide
|
|
118
|
+
changelog_uri: https://github.com/rspec-guide/rubocop-rspec-guide/blob/master/CHANGELOG.md
|
|
119
|
+
rdoc_options: []
|
|
120
|
+
require_paths:
|
|
121
|
+
- lib
|
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: 3.0.0
|
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
requirements: []
|
|
133
|
+
rubygems_version: 3.7.1
|
|
134
|
+
specification_version: 4
|
|
135
|
+
summary: Custom RuboCop cops based on the RSpec best practices guide
|
|
136
|
+
test_files: []
|