rubocop-rspec_parity 0.1.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/CLAUDE.md +37 -0
- data/README.md +10 -38
- data/config/default.yml +1 -6
- data/lib/rubocop/cop/rspec_parity/sufficient_contexts.rb +138 -18
- data/lib/rubocop/rspec_parity/version.rb +1 -1
- data/lib/rubocop_rspec_parity.rb +0 -1
- metadata +1 -2
- data/lib/rubocop/cop/rspec_parity/no_let_bang.rb +0 -36
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e75d169afdb6caa654d618ae15b194b3329b53c1151ce2cc492ed521a513327
|
|
4
|
+
data.tar.gz: 7ae507090fa12baa552224b911b5f50fb98096f87181880c98395b93368009cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c7e2fda565eafad49f2d7017edc91df2f12c200c821f59313afdc4c0e8221c7136e9197010a21ff6b2819c91b7a5cd72c7f71936aea2321ad389638bd830f70
|
|
7
|
+
data.tar.gz: b67f7496c9f0c483c82af7691579b3c9bb21efed23a4d7d50d70dac5fc5e3b2b7b531ecab0aedc660579fbbed659b1c38db0bf563c2f09ebf2f05be1d68665ae
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.0.0] - 2026-01-27
|
|
4
|
+
|
|
5
|
+
Added: `IgnoreMemoization` configuration option for `SufficientContexts` cop to ignore memoization patterns like `@var ||=` and `return @var if defined?(@var)`
|
|
6
|
+
Fixed: `SufficientContexts` cop now works with absolute file paths
|
|
7
|
+
Removed: `NoLetBang` cop
|
|
8
|
+
|
|
3
9
|
## [0.1.0] - 2026-01-15
|
|
4
10
|
|
|
5
11
|
- Initial release
|
data/CLAUDE.md
CHANGED
|
@@ -14,6 +14,43 @@
|
|
|
14
14
|
- All RuboCop violations are resolved
|
|
15
15
|
- All RSpec tests pass
|
|
16
16
|
- No new warnings or errors are introduced
|
|
17
|
+
- CHANGELOG.md has been updated if applicable (see below)
|
|
18
|
+
|
|
19
|
+
## Changelog Management
|
|
20
|
+
|
|
21
|
+
**Update CHANGELOG.md ONLY for:**
|
|
22
|
+
- New features (Added)
|
|
23
|
+
- Bug fixes (Fixed)
|
|
24
|
+
- Deprecations (Deprecated)
|
|
25
|
+
- Removed features (Removed)
|
|
26
|
+
- Dependency updates (Updated)
|
|
27
|
+
|
|
28
|
+
**DO NOT update for:**
|
|
29
|
+
- Refactoring or code quality improvements
|
|
30
|
+
- Minor documentation updates
|
|
31
|
+
- Internal implementation changes
|
|
32
|
+
|
|
33
|
+
**Format (compact, one line per change):**
|
|
34
|
+
```
|
|
35
|
+
Added: Allow passing custom decorator build strategy
|
|
36
|
+
Fixed: Better handle render in controller hooks
|
|
37
|
+
Updated: GraphQL and gems version dependencies
|
|
38
|
+
Removed: NoLetBang cop
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**During Release:**
|
|
42
|
+
- Move items from `[Unreleased]` to: `## [X.Y.Z] - YYYY-MM-DD`
|
|
43
|
+
- Leave `[Unreleased]` empty
|
|
44
|
+
|
|
45
|
+
## Release Process
|
|
46
|
+
|
|
47
|
+
To release a new version:
|
|
48
|
+
1. Update version in `lib/rubocop/rspec_parity/version.rb`
|
|
49
|
+
2. Update CHANGELOG.md (move [Unreleased] items to new version section)
|
|
50
|
+
3. Run tests: `bundle exec rspec && bundle exec rubocop`
|
|
51
|
+
4. Use rake task to release: `bundle exec rake release`
|
|
52
|
+
- This will create a git tag, push commits/tags, and push the gem to rubygems.org
|
|
53
|
+
5. **DO NOT** manually run `gem build` or `gem push` - use the rake task
|
|
17
54
|
|
|
18
55
|
## Git Commits
|
|
19
56
|
|
data/README.md
CHANGED
|
@@ -9,7 +9,6 @@ This plugin provides these custom cops:
|
|
|
9
9
|
- **RSpecParity/FileHasSpec**: Ensures every Ruby file in your app directory has a corresponding spec file
|
|
10
10
|
- **RSpecParity/PublicMethodHasSpec**: Ensures every public method has spec test coverage
|
|
11
11
|
- **RSpecParity/SufficientContexts**: Ensures specs have at least as many contexts as the method has branches (if/elsif/else, case/when, &&, ||, ternary operators)
|
|
12
|
-
- **RSpecParity/NoLetBang**: Disallows the use of `let!` in specs, encouraging explicit setup
|
|
13
12
|
|
|
14
13
|
## Examples
|
|
15
14
|
|
|
@@ -96,44 +95,21 @@ RSpec.describe UserCreator do
|
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
97
|
end
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### RSpecParity/NoLetBang
|
|
102
|
-
|
|
103
|
-
Disallows the use of `let!` in specs, encouraging explicit setup.
|
|
104
|
-
|
|
105
|
-
```ruby
|
|
106
|
-
# bad
|
|
107
|
-
RSpec.describe User do
|
|
108
|
-
let!(:user) { create(:user) }
|
|
109
98
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
# Memoization patterns are ignored by default
|
|
100
|
+
def cached_value
|
|
101
|
+
@cached_value ||= expensive_operation # Not counted as a branch
|
|
113
102
|
end
|
|
114
103
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
it 'does something' do
|
|
120
|
-
expect(user).to be_valid # Explicit reference
|
|
121
|
-
end
|
|
104
|
+
def cached_value
|
|
105
|
+
return @cached_value if defined?(@cached_value) # Not counted as a branch
|
|
106
|
+
@cached_value = expensive_operation
|
|
122
107
|
end
|
|
108
|
+
```
|
|
123
109
|
|
|
124
|
-
|
|
125
|
-
RSpec.describe User do
|
|
126
|
-
let(:user) { build(:user) }
|
|
127
|
-
|
|
128
|
-
before do
|
|
129
|
-
user.save! # Explicit setup in before block
|
|
130
|
-
end
|
|
110
|
+
**Configuration options:**
|
|
131
111
|
|
|
132
|
-
|
|
133
|
-
expect(user).to be_persisted
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
```
|
|
112
|
+
- `IgnoreMemoization` (default: `true`) - When enabled, common memoization patterns like `@var ||=` and `return @var if defined?(@var)` are not counted as branches. Set to `false` if you want to count these as branches.
|
|
137
113
|
|
|
138
114
|
## Assumptions
|
|
139
115
|
|
|
@@ -198,16 +174,12 @@ RSpecParity/PublicMethodHasSpec:
|
|
|
198
174
|
|
|
199
175
|
RSpecParity/SufficientContexts:
|
|
200
176
|
Enabled: true
|
|
177
|
+
IgnoreMemoization: true # Set to false to count memoization patterns as branches
|
|
201
178
|
Include:
|
|
202
179
|
- 'app/**/*.rb'
|
|
203
180
|
Exclude:
|
|
204
181
|
- 'app/assets/**/*'
|
|
205
182
|
- 'app/views/**/*'
|
|
206
|
-
|
|
207
|
-
RSpecParity/NoLetBang:
|
|
208
|
-
Enabled: true
|
|
209
|
-
Include:
|
|
210
|
-
- 'spec/**/*_spec.rb'
|
|
211
183
|
```
|
|
212
184
|
|
|
213
185
|
Run RuboCop as usual:
|
data/config/default.yml
CHANGED
|
@@ -23,15 +23,10 @@ RSpecParity/PublicMethodHasSpec:
|
|
|
23
23
|
RSpecParity/SufficientContexts:
|
|
24
24
|
Description: 'Ensures specs have at least as many contexts as the method has branches.'
|
|
25
25
|
Enabled: true
|
|
26
|
+
IgnoreMemoization: true
|
|
26
27
|
Include:
|
|
27
28
|
- 'app/**/*.rb'
|
|
28
29
|
Exclude:
|
|
29
30
|
- 'app/assets/**/*'
|
|
30
31
|
- 'app/views/**/*'
|
|
31
32
|
- 'app/javascript/**/*'
|
|
32
|
-
|
|
33
|
-
RSpecParity/NoLetBang:
|
|
34
|
-
Description: 'Disallows the use of `let!` in specs.'
|
|
35
|
-
Enabled: true
|
|
36
|
-
Include:
|
|
37
|
-
- 'spec/**/*_spec.rb'
|
|
@@ -58,6 +58,11 @@ module RuboCop
|
|
|
58
58
|
/^autosave_/
|
|
59
59
|
].freeze
|
|
60
60
|
|
|
61
|
+
def initialize(config = nil, options = nil)
|
|
62
|
+
super
|
|
63
|
+
@ignore_memoization = cop_config.fetch("IgnoreMemoization", true)
|
|
64
|
+
end
|
|
65
|
+
|
|
61
66
|
def on_def(node)
|
|
62
67
|
check_method(node)
|
|
63
68
|
end
|
|
@@ -81,6 +86,7 @@ module RuboCop
|
|
|
81
86
|
spec_content = File.read(spec_file)
|
|
82
87
|
contexts = count_contexts_for_method(spec_content, method_name(node))
|
|
83
88
|
|
|
89
|
+
return if contexts.zero? # Method has no specs at all - PublicMethodHasSpec handles this
|
|
84
90
|
return if contexts >= branches
|
|
85
91
|
|
|
86
92
|
missing = branches - contexts
|
|
@@ -104,7 +110,11 @@ module RuboCop
|
|
|
104
110
|
end
|
|
105
111
|
|
|
106
112
|
def in_covered_directory?
|
|
107
|
-
|
|
113
|
+
path = processed_source.path
|
|
114
|
+
# Handle both absolute and relative paths
|
|
115
|
+
COVERED_DIRECTORIES.any? do |dir|
|
|
116
|
+
path.start_with?(dir) || path.include?("/#{dir}/") || path.match?(%r{/#{Regexp.escape(dir)}$})
|
|
117
|
+
end
|
|
108
118
|
end
|
|
109
119
|
|
|
110
120
|
def excluded_method?(method_name)
|
|
@@ -115,37 +125,50 @@ module RuboCop
|
|
|
115
125
|
|
|
116
126
|
def spec_file_path
|
|
117
127
|
path = processed_source.path
|
|
118
|
-
|
|
128
|
+
# Handle both absolute and relative paths
|
|
129
|
+
path.sub(%r{/app/}, "/spec/").sub(%r{^app/}, "spec/").sub(/\.rb$/, "_spec.rb")
|
|
119
130
|
end
|
|
120
131
|
|
|
121
132
|
def count_branches(node)
|
|
122
133
|
branches = 0
|
|
123
|
-
elsif_nodes =
|
|
124
|
-
|
|
125
|
-
# First pass: collect all elsif nodes (if nodes in else branches)
|
|
126
|
-
node.each_descendant(:if) do |if_node|
|
|
127
|
-
elsif_nodes.add(if_node.else_branch) if if_node.else_branch&.if_type?
|
|
128
|
-
end
|
|
134
|
+
elsif_nodes = collect_elsif_nodes(node)
|
|
129
135
|
|
|
130
|
-
# Second pass: count branches, skipping elsif nodes
|
|
131
136
|
node.each_descendant do |descendant|
|
|
132
137
|
next if elsif_nodes.include?(descendant)
|
|
138
|
+
next if should_skip_node?(descendant)
|
|
133
139
|
|
|
134
140
|
branches += branch_count_for_node(descendant)
|
|
135
141
|
end
|
|
136
142
|
branches
|
|
137
143
|
end
|
|
138
144
|
|
|
145
|
+
def collect_elsif_nodes(node)
|
|
146
|
+
elsif_nodes = Set.new
|
|
147
|
+
node.each_descendant(:if) do |if_node|
|
|
148
|
+
elsif_nodes.add(if_node.else_branch) if if_node.else_branch&.if_type?
|
|
149
|
+
end
|
|
150
|
+
elsif_nodes
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def should_skip_node?(node)
|
|
154
|
+
@ignore_memoization && memoization_pattern?(node)
|
|
155
|
+
end
|
|
156
|
+
|
|
139
157
|
def branch_count_for_node(node)
|
|
140
158
|
case node.type
|
|
141
159
|
when :if then count_if_branches(node)
|
|
142
160
|
when :case then count_case_branches(node)
|
|
143
161
|
when :and, :or then 1
|
|
144
|
-
when :
|
|
162
|
+
when :or_asgn, :and_asgn then 2 # ||= and &&= create 2 branches (set vs already set)
|
|
163
|
+
when :send then send_node_branch_count(node)
|
|
145
164
|
else 0
|
|
146
165
|
end
|
|
147
166
|
end
|
|
148
167
|
|
|
168
|
+
def send_node_branch_count(node)
|
|
169
|
+
node.method?(:&) || node.method?(:|) ? 1 : 0
|
|
170
|
+
end
|
|
171
|
+
|
|
149
172
|
def count_if_branches(node)
|
|
150
173
|
# if/else is 2 branches, each elsif adds 1
|
|
151
174
|
branches = 2
|
|
@@ -164,37 +187,55 @@ module RuboCop
|
|
|
164
187
|
when_count + (has_else ? 1 : 0)
|
|
165
188
|
end
|
|
166
189
|
|
|
167
|
-
# rubocop:disable Metrics/MethodLength
|
|
168
190
|
def count_contexts_for_method(spec_content, method_name)
|
|
169
191
|
method_pattern = Regexp.escape(method_name)
|
|
192
|
+
context_count, has_examples = parse_spec_content(spec_content, method_pattern)
|
|
193
|
+
|
|
194
|
+
# If no contexts but has examples, count as 1 scenario
|
|
195
|
+
context_count.zero? && has_examples ? 1 : context_count
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# rubocop:disable Metrics/MethodLength
|
|
199
|
+
def parse_spec_content(spec_content, method_pattern)
|
|
170
200
|
in_method_block = false
|
|
171
201
|
context_count = 0
|
|
202
|
+
has_examples = false
|
|
172
203
|
base_indent = 0
|
|
173
204
|
|
|
174
205
|
spec_content.each_line do |line|
|
|
175
206
|
current_indent = line[/^\s*/].length
|
|
176
207
|
|
|
177
|
-
# Entering a describe block for this method
|
|
178
208
|
if matches_method_describe?(line, method_pattern)
|
|
179
209
|
in_method_block = true
|
|
180
210
|
base_indent = current_indent
|
|
181
|
-
# Don't count the describe itself, only nested contexts
|
|
182
211
|
next
|
|
183
212
|
end
|
|
184
213
|
|
|
185
|
-
# Process lines inside the method block
|
|
186
214
|
if in_method_block
|
|
187
|
-
in_method_block =
|
|
188
|
-
|
|
215
|
+
context_count, has_examples, in_method_block = process_method_block_line(
|
|
216
|
+
line, current_indent, base_indent, context_count, has_examples
|
|
217
|
+
)
|
|
189
218
|
elsif matches_context_pattern?(line, method_pattern)
|
|
190
219
|
context_count += 1
|
|
191
220
|
end
|
|
192
221
|
end
|
|
193
222
|
|
|
194
|
-
context_count
|
|
223
|
+
[context_count, has_examples]
|
|
195
224
|
end
|
|
196
|
-
|
|
197
225
|
# rubocop:enable Metrics/MethodLength
|
|
226
|
+
|
|
227
|
+
def process_method_block_line(line, current_indent, base_indent, context_count, has_examples)
|
|
228
|
+
in_method_block = !exiting_block?(line, current_indent, base_indent)
|
|
229
|
+
|
|
230
|
+
if nested_context?(line)
|
|
231
|
+
context_count += 1
|
|
232
|
+
elsif nested_example?(line)
|
|
233
|
+
has_examples = true
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
[context_count, has_examples, in_method_block]
|
|
237
|
+
end
|
|
238
|
+
|
|
198
239
|
def matches_method_describe?(line, method_pattern)
|
|
199
240
|
line =~ /^\s*describe\s+['"](?:#|\.)?#{method_pattern}['"]/ ||
|
|
200
241
|
line =~ /^\s*describe\s+:#{method_pattern}/
|
|
@@ -208,6 +249,10 @@ module RuboCop
|
|
|
208
249
|
line =~ /^\s*(?:context|describe)\s+/
|
|
209
250
|
end
|
|
210
251
|
|
|
252
|
+
def nested_example?(line)
|
|
253
|
+
line =~ /^\s*(?:it|example|specify)\s+/
|
|
254
|
+
end
|
|
255
|
+
|
|
211
256
|
def exiting_block?(line, current_indent, base_indent)
|
|
212
257
|
current_indent <= base_indent && line =~ /^\s*(?:describe|context|end)/
|
|
213
258
|
end
|
|
@@ -221,6 +266,81 @@ module RuboCop
|
|
|
221
266
|
else "#{word}s"
|
|
222
267
|
end
|
|
223
268
|
end
|
|
269
|
+
|
|
270
|
+
def memoization_pattern?(node)
|
|
271
|
+
# Pattern: @var ||= value
|
|
272
|
+
return true if or_asgn_ivar_pattern?(node)
|
|
273
|
+
|
|
274
|
+
# Pattern: return @var if defined?(@var)
|
|
275
|
+
return true if defined_check_pattern?(node)
|
|
276
|
+
|
|
277
|
+
# Pattern: @var = value if @var.nil? or similar
|
|
278
|
+
return true if nil_check_pattern?(node)
|
|
279
|
+
|
|
280
|
+
# Pattern: || with instance variable (part of @var ||= which creates both :or and :or_asgn nodes)
|
|
281
|
+
return true if or_with_ivar_pattern?(node)
|
|
282
|
+
|
|
283
|
+
false
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @var ||= value
|
|
287
|
+
def or_asgn_ivar_pattern?(node)
|
|
288
|
+
node.or_asgn_type? && node.children[0]&.ivasgn_type?
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# return @var if defined?(@var)
|
|
292
|
+
def defined_check_pattern?(node)
|
|
293
|
+
return false unless node.if_type?
|
|
294
|
+
|
|
295
|
+
condition = node.condition
|
|
296
|
+
return false unless condition&.defined_type?
|
|
297
|
+
|
|
298
|
+
# Check if it's checking an instance variable
|
|
299
|
+
condition.children[0]&.ivar_type?
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# @var = value if @var.nil? or @var = value unless @var
|
|
303
|
+
def nil_check_pattern?(node)
|
|
304
|
+
return false unless node.if_type?
|
|
305
|
+
|
|
306
|
+
condition = node.condition
|
|
307
|
+
body = node.body
|
|
308
|
+
|
|
309
|
+
# Check if body is an ivasgn
|
|
310
|
+
return false unless body&.ivasgn_type?
|
|
311
|
+
|
|
312
|
+
ivar_name = body.children[0]
|
|
313
|
+
|
|
314
|
+
# Check if condition checks the same ivar for nil
|
|
315
|
+
checks_same_ivar_for_nil?(condition, ivar_name)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def checks_same_ivar_for_nil?(condition, ivar_name)
|
|
319
|
+
return false unless condition
|
|
320
|
+
|
|
321
|
+
nil_check?(condition, ivar_name) || negation_check?(condition, ivar_name)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def nil_check?(condition, ivar_name)
|
|
325
|
+
return false unless condition.send_type? && condition.method?(:nil?)
|
|
326
|
+
|
|
327
|
+
condition.receiver&.ivar_type? && condition.receiver.children[0] == ivar_name
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def negation_check?(condition, ivar_name)
|
|
331
|
+
return false unless condition.send_type? && condition.method?(:!)
|
|
332
|
+
|
|
333
|
+
receiver = condition.receiver
|
|
334
|
+
receiver&.ivar_type? && receiver.children[0] == ivar_name
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# || operator with instance variable on left side
|
|
338
|
+
def or_with_ivar_pattern?(node)
|
|
339
|
+
return false unless node.or_type?
|
|
340
|
+
|
|
341
|
+
left = node.children[0]
|
|
342
|
+
left&.ivar_type?
|
|
343
|
+
end
|
|
224
344
|
end
|
|
225
345
|
end
|
|
226
346
|
end
|
data/lib/rubocop_rspec_parity.rb
CHANGED
|
@@ -5,6 +5,5 @@ require "rubocop"
|
|
|
5
5
|
require_relative "rubocop/rspec_parity"
|
|
6
6
|
require_relative "rubocop/rspec_parity/version"
|
|
7
7
|
require_relative "rubocop/rspec_parity/plugin"
|
|
8
|
-
require_relative "rubocop/cop/rspec_parity/no_let_bang"
|
|
9
8
|
require_relative "rubocop/cop/rspec_parity/public_method_has_spec"
|
|
10
9
|
require_relative "rubocop/cop/rspec_parity/sufficient_contexts"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-rspec_parity
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Povilas Jurcys
|
|
@@ -54,7 +54,6 @@ files:
|
|
|
54
54
|
- Rakefile
|
|
55
55
|
- config/default.yml
|
|
56
56
|
- lib/rubocop-rspec_parity.rb
|
|
57
|
-
- lib/rubocop/cop/rspec_parity/no_let_bang.rb
|
|
58
57
|
- lib/rubocop/cop/rspec_parity/public_method_has_spec.rb
|
|
59
58
|
- lib/rubocop/cop/rspec_parity/sufficient_contexts.rb
|
|
60
59
|
- lib/rubocop/rspec_parity.rb
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Cop
|
|
5
|
-
module RSpecParity
|
|
6
|
-
# Disallows the use of `let!` in specs.
|
|
7
|
-
#
|
|
8
|
-
# `let!` creates implicit setup that runs before each example,
|
|
9
|
-
# which can make tests harder to understand and debug.
|
|
10
|
-
# Prefer using `let` with explicit references or `before` blocks.
|
|
11
|
-
#
|
|
12
|
-
# @example
|
|
13
|
-
# # bad
|
|
14
|
-
# let!(:user) { create(:user) }
|
|
15
|
-
#
|
|
16
|
-
# # good
|
|
17
|
-
# let(:user) { create(:user) }
|
|
18
|
-
#
|
|
19
|
-
# # good
|
|
20
|
-
# before { create(:user) }
|
|
21
|
-
#
|
|
22
|
-
class NoLetBang < Base
|
|
23
|
-
MSG = "Do not use `let!`. Use `let` with explicit reference or `before` block instead."
|
|
24
|
-
|
|
25
|
-
# @!method let_bang?(node)
|
|
26
|
-
def_node_matcher :let_bang?, "(send nil? :let! ...)"
|
|
27
|
-
|
|
28
|
-
def on_send(node)
|
|
29
|
-
return unless let_bang?(node)
|
|
30
|
-
|
|
31
|
-
add_offense(node)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|