shirobai-rspec 2026.0709.0000
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/LICENSE.txt +21 -0
- data/lib/shirobai/cop/rspec/described_class.rb +213 -0
- data/lib/shirobai/cop/rspec/duplicated_metadata.rb +70 -0
- data/lib/shirobai/cop/rspec/empty_example_group.rb +156 -0
- data/lib/shirobai/cop/rspec/empty_line_after_example.rb +50 -0
- data/lib/shirobai/cop/rspec/empty_line_after_example_group.rb +46 -0
- data/lib/shirobai/cop/rspec/empty_line_after_final_let.rb +47 -0
- data/lib/shirobai/cop/rspec/empty_line_after_hook.rb +48 -0
- data/lib/shirobai/cop/rspec/empty_line_after_subject.rb +46 -0
- data/lib/shirobai/cop/rspec/empty_line_separation_support.rb +61 -0
- data/lib/shirobai/cop/rspec/empty_metadata.rb +58 -0
- data/lib/shirobai/cop/rspec/focus.rb +110 -0
- data/lib/shirobai/cop/rspec/let_setup.rb +63 -0
- data/lib/shirobai/cop/rspec/metadata_style.rb +200 -0
- data/lib/shirobai/cop/rspec/metadata_support.rb +109 -0
- data/lib/shirobai/cop/rspec/multiple_memoized_helpers.rb +96 -0
- data/lib/shirobai/cop/rspec/named_subject.rb +71 -0
- data/lib/shirobai/cop/rspec/pending_without_reason.rb +129 -0
- data/lib/shirobai/cop/rspec/repeated_description.rb +129 -0
- data/lib/shirobai/cop/rspec/repeated_example.rb +129 -0
- data/lib/shirobai/cop/rspec/scattered_setup.rb +133 -0
- data/lib/shirobai/cop/rspec/send_candidate_support.rb +48 -0
- data/lib/shirobai/cop/rspec/sort_metadata.rb +88 -0
- data/lib/shirobai/cop/rspec/variable_definition.rb +81 -0
- data/lib/shirobai/cop/rspec/variable_name.rb +92 -0
- data/lib/shirobai/rspec/node_locator.rb +126 -0
- data/lib/shirobai/rspec/version.rb +9 -0
- data/lib/shirobai-rspec.rb +175 -0
- metadata +93 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7a73330fe18d5f58a09e051463f6072c5dd4fefc91600459640adc00d3cc5ce2
|
|
4
|
+
data.tar.gz: dada93df314d81800637e9d14a93940d9cd67097d5671fe636d261c6515b9b4f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1b9225390133b623be2a87797d411001de0a7cb415245ca0e1c5055b5d4b70aa9f1bd658d20030ce07698423a116a8b9014e5fdebf5131c61afb6bba0bbb74d5
|
|
7
|
+
data.tar.gz: 54689cbadf3ac1cda17865f99c481a2e9223ca3c926fd7141f6821a7bf7c7b7f828c88736837ea8aa7e7deabde4c55134de87bff5a05c582e7e7454836cfbbea
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fusagiko / takayamaki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/DescribedClass`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# Rust identifies candidate `describe(Const)` blocks on the shared walk.
|
|
10
|
+
# This wrapper locates the parser block node and runs stock's full
|
|
11
|
+
# detection + autocorrect VERBATIM: `find_usage` recursion with
|
|
12
|
+
# scope-change guards, `collapse_namespace`, `described_constant`,
|
|
13
|
+
# and all config axes (EnforcedStyle, SkipBlocks, OnlyStaticConstants).
|
|
14
|
+
#
|
|
15
|
+
# Probed quirks live as differential specs in
|
|
16
|
+
# described_class_edge_cases_spec.rb.
|
|
17
|
+
class DescribedClass < RuboCop::Cop::Base
|
|
18
|
+
extend RuboCop::Cop::AutoCorrector
|
|
19
|
+
include RuboCop::Cop::ConfigurableEnforcedStyle
|
|
20
|
+
include RuboCop::Cop::RSpec::Namespace
|
|
21
|
+
include RuboCop::RSpec::Language
|
|
22
|
+
include Shirobai::Cop::BundleEligible
|
|
23
|
+
|
|
24
|
+
DESCRIBED_CLASS = 'described_class'
|
|
25
|
+
MSG = 'Use `%<replacement>s` instead of `%<src>s`.'
|
|
26
|
+
|
|
27
|
+
def self.cop_name = "RSpec/DescribedClass"
|
|
28
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
29
|
+
|
|
30
|
+
# No bundle_args needed -- the Rust side does not pack config for
|
|
31
|
+
# this cop (all config axes are handled in the Ruby wrapper).
|
|
32
|
+
def self.bundle_args(_config)
|
|
33
|
+
[]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @!method common_instance_exec_closure?(node)
|
|
37
|
+
def_node_matcher :common_instance_exec_closure?, <<~PATTERN
|
|
38
|
+
(block
|
|
39
|
+
{
|
|
40
|
+
(send (const nil? {:Class :Module :Struct}) :new ...)
|
|
41
|
+
(send (const nil? :Data) :define ...)
|
|
42
|
+
(send _ {:class_eval :module_eval :instance_eval} ...)
|
|
43
|
+
(send _ {:class_exec :module_exec :instance_exec} ...)
|
|
44
|
+
}
|
|
45
|
+
...
|
|
46
|
+
)
|
|
47
|
+
PATTERN
|
|
48
|
+
|
|
49
|
+
# @!method rspec_block?(node)
|
|
50
|
+
def_node_matcher :rspec_block?,
|
|
51
|
+
'(any_block (send #rspec? #ALL.all ...) ...)'
|
|
52
|
+
|
|
53
|
+
# @!method scope_changing_syntax?(node)
|
|
54
|
+
def_node_matcher :scope_changing_syntax?, '{def class module}'
|
|
55
|
+
|
|
56
|
+
# @!method described_constant(node)
|
|
57
|
+
def_node_matcher :described_constant, <<~PATTERN
|
|
58
|
+
(block (send _ :describe $(const ...) ...) (args) $_)
|
|
59
|
+
PATTERN
|
|
60
|
+
|
|
61
|
+
# @!method contains_described_class?(node)
|
|
62
|
+
def_node_search :contains_described_class?,
|
|
63
|
+
'(send nil? :described_class)'
|
|
64
|
+
|
|
65
|
+
def on_new_investigation
|
|
66
|
+
RuboCop::RSpec::Language.config = config["RSpec"]["Language"]
|
|
67
|
+
ranges = resolved_candidates
|
|
68
|
+
return if ranges.nil? || ranges.empty?
|
|
69
|
+
|
|
70
|
+
source = bundle_eligible? ? processed_source.raw_source : processed_source.buffer.source
|
|
71
|
+
off = SourceOffsets.for(source)
|
|
72
|
+
keys = ranges.map { |(start, fin)| [off[start], off[fin]] }
|
|
73
|
+
located = Shirobai::RSpec::NodeLocator.locate(processed_source, keys)
|
|
74
|
+
|
|
75
|
+
keys.each do |key|
|
|
76
|
+
node = located[key]
|
|
77
|
+
process_candidate(node) if node&.block_type?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Stock's on_block logic, copied verbatim.
|
|
84
|
+
def process_candidate(node)
|
|
85
|
+
@described_class, body = described_constant(node)
|
|
86
|
+
return unless body
|
|
87
|
+
|
|
88
|
+
find_usage(body) do |match|
|
|
89
|
+
msg = message(match.const_name)
|
|
90
|
+
add_offense(match, message: msg) do |corrector|
|
|
91
|
+
autocorrect(corrector, match)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def autocorrect(corrector, match)
|
|
97
|
+
replacement = if style == :described_class
|
|
98
|
+
DESCRIBED_CLASS
|
|
99
|
+
else
|
|
100
|
+
@described_class.const_name
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
corrector.replace(match, replacement)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def find_usage(node, &block)
|
|
107
|
+
yield(node) if offensive?(node)
|
|
108
|
+
return if scope_change?(node) || allowed?(node)
|
|
109
|
+
|
|
110
|
+
node.each_child_node do |child|
|
|
111
|
+
find_usage(child, &block)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def allowed?(node)
|
|
116
|
+
node.const_type? && only_static_constants?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def message(offense)
|
|
120
|
+
if style == :described_class
|
|
121
|
+
format(MSG, replacement: DESCRIBED_CLASS, src: offense)
|
|
122
|
+
else
|
|
123
|
+
format(MSG, replacement: @described_class.const_name,
|
|
124
|
+
src: DESCRIBED_CLASS)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def scope_change?(node)
|
|
129
|
+
scope_changing_syntax?(node) ||
|
|
130
|
+
common_instance_exec_closure?(node) ||
|
|
131
|
+
skippable_block?(node)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def skippable_block?(node)
|
|
135
|
+
return false unless cop_config['SkipBlocks']
|
|
136
|
+
|
|
137
|
+
node.any_block_type? && !rspec_block?(node)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def only_static_constants?
|
|
141
|
+
cop_config.fetch('OnlyStaticConstants', true)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def offensive?(node)
|
|
145
|
+
if style == :described_class
|
|
146
|
+
offensive_described_class?(node)
|
|
147
|
+
else
|
|
148
|
+
node.send_type? && node.method?(:described_class)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def offensive_described_class?(node)
|
|
153
|
+
return false unless node.const_type?
|
|
154
|
+
|
|
155
|
+
# E.g. `described_class::CONSTANT`
|
|
156
|
+
return false if contains_described_class?(node)
|
|
157
|
+
|
|
158
|
+
nearest_described_class, = node.each_ancestor(:block)
|
|
159
|
+
.map { |ancestor| described_constant(ancestor) }.find(&:itself)
|
|
160
|
+
|
|
161
|
+
return false if nearest_described_class.equal?(node)
|
|
162
|
+
|
|
163
|
+
full_const_name(nearest_described_class) == full_const_name(node)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def full_const_name(node)
|
|
167
|
+
symbolized_namespace = namespace(node).map(&:to_sym)
|
|
168
|
+
collapse_namespace(symbolized_namespace, const_name(node))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @param namespace [Array<Symbol>]
|
|
172
|
+
# @param const [Array<Symbol>]
|
|
173
|
+
# @return [Array<Symbol>]
|
|
174
|
+
def collapse_namespace(namespace, const)
|
|
175
|
+
return const if namespace.empty? || const.first.nil?
|
|
176
|
+
|
|
177
|
+
start = [0, (namespace.length - const.length)].max
|
|
178
|
+
max = namespace.length
|
|
179
|
+
intersection = (start..max).find do |shift|
|
|
180
|
+
namespace[shift, max - shift] == const[0, max - shift]
|
|
181
|
+
end
|
|
182
|
+
[*namespace[0, intersection], *const]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# @param node [RuboCop::AST::Node]
|
|
186
|
+
# @return [Array<Symbol>]
|
|
187
|
+
def const_name(node)
|
|
188
|
+
namespace = node.namespace
|
|
189
|
+
name = node.short_name
|
|
190
|
+
if !namespace
|
|
191
|
+
[name]
|
|
192
|
+
elsif namespace.const_type?
|
|
193
|
+
[*const_name(namespace), name]
|
|
194
|
+
elsif %i[lvar cbase send].include?(namespace.type)
|
|
195
|
+
[nil, name]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# --- Resolution ---
|
|
200
|
+
|
|
201
|
+
def resolved_candidates
|
|
202
|
+
if bundle_eligible?
|
|
203
|
+
ranges = Dispatch.offenses_for(processed_source, config, :rspec_described_class)
|
|
204
|
+
return ranges unless ranges.nil?
|
|
205
|
+
end
|
|
206
|
+
Shirobai.check_rspec_described_class(
|
|
207
|
+
processed_source.buffer.source, *Shirobai::RSpec.segment(config)
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/DuplicatedMetadata`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# Rust supplies the shared metadata-anchor block ranges; this wrapper
|
|
10
|
+
# relocates each parser block node and runs stock's `on_metadata` plus
|
|
11
|
+
# autocorrect VERBATIM, so structural `eql?` duplicate detection and the
|
|
12
|
+
# comma/space-aware removal match byte for byte.
|
|
13
|
+
class DuplicatedMetadata < RuboCop::Cop::Base
|
|
14
|
+
extend RuboCop::Cop::AutoCorrector
|
|
15
|
+
|
|
16
|
+
include RuboCop::RSpec::Language
|
|
17
|
+
include Shirobai::Cop::RSpec::MetadataSupport
|
|
18
|
+
include RuboCop::Cop::RangeHelp
|
|
19
|
+
|
|
20
|
+
MSG = "Avoid duplicated metadata."
|
|
21
|
+
|
|
22
|
+
def self.cop_name = "RSpec/DuplicatedMetadata"
|
|
23
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
24
|
+
|
|
25
|
+
# Config-less; the shared anchor list needs no per-cop settings.
|
|
26
|
+
def self.bundle_args(_config)
|
|
27
|
+
[]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def metadata_slot = :rspec_duplicated_metadata
|
|
31
|
+
|
|
32
|
+
# --- stock's private methods, copied verbatim (rubocop-rspec 3.10.2) ---
|
|
33
|
+
|
|
34
|
+
def on_metadata(symbols, _hash)
|
|
35
|
+
symbols.each do |symbol|
|
|
36
|
+
on_metadata_symbol(symbol)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def on_metadata_symbol(node)
|
|
43
|
+
return unless duplicated?(node)
|
|
44
|
+
|
|
45
|
+
add_offense(node) do |corrector|
|
|
46
|
+
autocorrect(corrector, node)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def autocorrect(corrector, node)
|
|
51
|
+
corrector.remove(
|
|
52
|
+
range_with_surrounding_comma(
|
|
53
|
+
range_with_surrounding_space(
|
|
54
|
+
node.source_range,
|
|
55
|
+
side: :left
|
|
56
|
+
),
|
|
57
|
+
:left
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def duplicated?(node)
|
|
63
|
+
node.left_siblings.any? do |sibling|
|
|
64
|
+
sibling.eql?(node)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/EmptyExampleGroup`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# Architecture B (relocate-and-dispatch): Rust identifies candidate
|
|
10
|
+
# example-group blocks on the shared walk; this wrapper locates the
|
|
11
|
+
# parser-gem block node via `NodeLocator` and runs stock's detection
|
|
12
|
+
# logic VERBATIM. The mutually recursive `examples?` matcher is deep
|
|
13
|
+
# parser-AST structural matching that cannot be reproduced bytewise.
|
|
14
|
+
# The autocorrect is simple (remove whole lines via `range_by_whole_lines`).
|
|
15
|
+
#
|
|
16
|
+
# Stock entry method `on_block` is renamed to `process_candidate` so
|
|
17
|
+
# the Commissioner never dispatches a per-node `on_block`; only
|
|
18
|
+
# `on_new_investigation` is a real callback.
|
|
19
|
+
class EmptyExampleGroup < RuboCop::Cop::Base
|
|
20
|
+
extend RuboCop::Cop::AutoCorrector
|
|
21
|
+
|
|
22
|
+
include RuboCop::RSpec::Language
|
|
23
|
+
include RuboCop::Cop::RSpec::InsideExample
|
|
24
|
+
include RuboCop::Cop::RangeHelp
|
|
25
|
+
include Shirobai::Cop::BundleEligible
|
|
26
|
+
|
|
27
|
+
MSG = "Empty example group detected."
|
|
28
|
+
|
|
29
|
+
def self.cop_name = "RSpec/EmptyExampleGroup"
|
|
30
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
31
|
+
|
|
32
|
+
# Config-less (the segment's role lists cover everything).
|
|
33
|
+
def self.bundle_args(_config)
|
|
34
|
+
[]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# --- stock node-pattern matchers, copied verbatim (rubocop-rspec 3.10.2) ---
|
|
38
|
+
|
|
39
|
+
# @!method example_group_body(node)
|
|
40
|
+
def_node_matcher :example_group_body, <<~PATTERN
|
|
41
|
+
(block (send #rspec? #ExampleGroups.all ...) args $_)
|
|
42
|
+
PATTERN
|
|
43
|
+
|
|
44
|
+
# @!method example_or_group_or_include?(node)
|
|
45
|
+
def_node_matcher :example_or_group_or_include?, <<~PATTERN
|
|
46
|
+
{
|
|
47
|
+
(block
|
|
48
|
+
(send #rspec? {#Examples.all #ExampleGroups.all #Includes.all} ...)
|
|
49
|
+
...)
|
|
50
|
+
(send nil? {#Examples.all #Includes.all} ...)
|
|
51
|
+
}
|
|
52
|
+
PATTERN
|
|
53
|
+
|
|
54
|
+
# @!method examples_inside_block?(node)
|
|
55
|
+
def_node_matcher :examples_inside_block?, <<~PATTERN
|
|
56
|
+
(block !(send nil? #Hooks.all ...) _ #examples?)
|
|
57
|
+
PATTERN
|
|
58
|
+
|
|
59
|
+
# @!method examples_directly_or_in_block?(node)
|
|
60
|
+
def_node_matcher :examples_directly_or_in_block?, <<~PATTERN
|
|
61
|
+
{
|
|
62
|
+
#example_or_group_or_include?
|
|
63
|
+
#examples_inside_block?
|
|
64
|
+
}
|
|
65
|
+
PATTERN
|
|
66
|
+
|
|
67
|
+
# @!method examples?(node)
|
|
68
|
+
def_node_matcher :examples?, <<~PATTERN
|
|
69
|
+
{
|
|
70
|
+
#examples_directly_or_in_block?
|
|
71
|
+
#examples_in_branches?
|
|
72
|
+
(begin <#examples_directly_or_in_block? ...>)
|
|
73
|
+
(begin <#examples_in_branches? ...>)
|
|
74
|
+
}
|
|
75
|
+
PATTERN
|
|
76
|
+
|
|
77
|
+
def on_new_investigation
|
|
78
|
+
RuboCop::RSpec::Language.config = config["RSpec"]["Language"]
|
|
79
|
+
ranges = resolved_candidates
|
|
80
|
+
return if ranges.nil? || ranges.empty?
|
|
81
|
+
|
|
82
|
+
source = bundle_eligible? ? processed_source.raw_source : processed_source.buffer.source
|
|
83
|
+
off = SourceOffsets.for(source)
|
|
84
|
+
keys = ranges.map { |(start, fin)| [off[start], off[fin]] }
|
|
85
|
+
located = Shirobai::RSpec::NodeLocator.locate(processed_source, keys)
|
|
86
|
+
|
|
87
|
+
keys.each do |key|
|
|
88
|
+
node = located[key]
|
|
89
|
+
process_candidate(node) if node&.block_type?
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def resolved_candidates
|
|
96
|
+
if bundle_eligible?
|
|
97
|
+
candidates = Dispatch.offenses_for(processed_source, config, :rspec_empty_example_group)
|
|
98
|
+
return candidates unless candidates.nil?
|
|
99
|
+
end
|
|
100
|
+
Shirobai.check_rspec_empty_example_group(
|
|
101
|
+
processed_source.buffer.source, *Shirobai::RSpec.segment(config)
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Stock `on_block`, renamed so the Commissioner never dispatches it.
|
|
106
|
+
def process_candidate(node)
|
|
107
|
+
return if node.each_ancestor(:any_def).any?
|
|
108
|
+
return if inside_example?(node)
|
|
109
|
+
|
|
110
|
+
example_group_body(node) do |body|
|
|
111
|
+
next unless offensive?(body)
|
|
112
|
+
|
|
113
|
+
add_offense(node.send_node) do |corrector|
|
|
114
|
+
corrector.remove(removed_range(node))
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# --- stock private methods, copied verbatim (rubocop-rspec 3.10.2) ---
|
|
120
|
+
|
|
121
|
+
def offensive?(body)
|
|
122
|
+
return true unless body
|
|
123
|
+
return false if conditionals_with_examples?(body)
|
|
124
|
+
|
|
125
|
+
if body.type?(:if, :case)
|
|
126
|
+
!examples_in_branches?(body)
|
|
127
|
+
else
|
|
128
|
+
!examples?(body)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def conditionals_with_examples?(body)
|
|
133
|
+
return false unless body.type?(:begin, :case)
|
|
134
|
+
|
|
135
|
+
body.each_descendant(:if, :case).any? do |condition_node|
|
|
136
|
+
examples_in_branches?(condition_node)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def examples_in_branches?(condition_node)
|
|
141
|
+
return false unless condition_node
|
|
142
|
+
return false unless condition_node.type?(:if, :case)
|
|
143
|
+
|
|
144
|
+
condition_node.branches.any? { |branch| examples?(branch) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def removed_range(node)
|
|
148
|
+
range_by_whole_lines(
|
|
149
|
+
node.source_range,
|
|
150
|
+
include_final_newline: true
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/EmptyLineAfterExample`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# The Rust rule flags every plain-block example (`it`/`specify`/`its`/...)
|
|
10
|
+
# that has a following sibling in a `:begin` sequence, minus the allowed
|
|
11
|
+
# consecutive-one-liner shape (`AllowConsecutiveOneLiners`, default true).
|
|
12
|
+
# The shared support module replays stock's comment/blank/directive walk
|
|
13
|
+
# and autocorrect. Probed quirks live in
|
|
14
|
+
# empty_line_after_example_edge_cases_spec.rb.
|
|
15
|
+
class EmptyLineAfterExample < RuboCop::Cop::Base
|
|
16
|
+
extend RuboCop::Cop::AutoCorrector
|
|
17
|
+
include Shirobai::Cop::BundleEligible
|
|
18
|
+
include Shirobai::Cop::RSpec::EmptyLineSeparationSupport
|
|
19
|
+
|
|
20
|
+
MSG = "Add an empty line after `%<example>s`."
|
|
21
|
+
|
|
22
|
+
def self.cop_name = "RSpec/EmptyLineAfterExample"
|
|
23
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
24
|
+
|
|
25
|
+
# `AllowConsecutiveOneLiners` (default true) -> segment num.
|
|
26
|
+
def self.bundle_args(config)
|
|
27
|
+
[config.for_badge(badge).fetch("AllowConsecutiveOneLiners", true) ? 1 : 0]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_new_investigation
|
|
31
|
+
emit_empty_line_offenses(resolved_offenses) do |method|
|
|
32
|
+
format(MSG, example: method)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def resolved_offenses
|
|
39
|
+
if bundle_eligible?
|
|
40
|
+
offenses = Dispatch.offenses_for(processed_source, config, :rspec_empty_line_after_example)
|
|
41
|
+
return offenses unless offenses.nil?
|
|
42
|
+
end
|
|
43
|
+
Shirobai.check_rspec_empty_line_after_example(
|
|
44
|
+
processed_source.buffer.source, *Shirobai::RSpec.segment(config)
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/EmptyLineAfterExampleGroup`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# Flags every plain-block spec group (`describe`/`context`/... AND
|
|
10
|
+
# `shared_examples`/`shared_context` with an RSpec receiver) that has a
|
|
11
|
+
# following sibling in a `:begin` sequence. Config-less.
|
|
12
|
+
class EmptyLineAfterExampleGroup < RuboCop::Cop::Base
|
|
13
|
+
extend RuboCop::Cop::AutoCorrector
|
|
14
|
+
include Shirobai::Cop::BundleEligible
|
|
15
|
+
include Shirobai::Cop::RSpec::EmptyLineSeparationSupport
|
|
16
|
+
|
|
17
|
+
MSG = "Add an empty line after `%<example_group>s`."
|
|
18
|
+
|
|
19
|
+
def self.cop_name = "RSpec/EmptyLineAfterExampleGroup"
|
|
20
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
21
|
+
|
|
22
|
+
def self.bundle_args(_config)
|
|
23
|
+
[]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_new_investigation
|
|
27
|
+
emit_empty_line_offenses(resolved_offenses) do |method|
|
|
28
|
+
format(MSG, example_group: method)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def resolved_offenses
|
|
35
|
+
if bundle_eligible?
|
|
36
|
+
offenses = Dispatch.offenses_for(processed_source, config, :rspec_empty_line_after_example_group)
|
|
37
|
+
return offenses unless offenses.nil?
|
|
38
|
+
end
|
|
39
|
+
Shirobai.check_rspec_empty_line_after_example_group(
|
|
40
|
+
processed_source.buffer.source, *Shirobai::RSpec.segment(config)
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/EmptyLineAfterFinalLet`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# For every plain-block example/shared group or `include_*` block, the
|
|
10
|
+
# Rust rule finds the last `let?` among its direct body children (block
|
|
11
|
+
# form or the `let(:x, &blk)` send form) and flags it when it is not the
|
|
12
|
+
# body's last child. Config-less.
|
|
13
|
+
class EmptyLineAfterFinalLet < RuboCop::Cop::Base
|
|
14
|
+
extend RuboCop::Cop::AutoCorrector
|
|
15
|
+
include Shirobai::Cop::BundleEligible
|
|
16
|
+
include Shirobai::Cop::RSpec::EmptyLineSeparationSupport
|
|
17
|
+
|
|
18
|
+
MSG = "Add an empty line after the last `%<let>s`."
|
|
19
|
+
|
|
20
|
+
def self.cop_name = "RSpec/EmptyLineAfterFinalLet"
|
|
21
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
22
|
+
|
|
23
|
+
def self.bundle_args(_config)
|
|
24
|
+
[]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def on_new_investigation
|
|
28
|
+
emit_empty_line_offenses(resolved_offenses) do |method|
|
|
29
|
+
format(MSG, let: method)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def resolved_offenses
|
|
36
|
+
if bundle_eligible?
|
|
37
|
+
offenses = Dispatch.offenses_for(processed_source, config, :rspec_empty_line_after_final_let)
|
|
38
|
+
return offenses unless offenses.nil?
|
|
39
|
+
end
|
|
40
|
+
Shirobai.check_rspec_empty_line_after_final_let(
|
|
41
|
+
processed_source.buffer.source, *Shirobai::RSpec.segment(config)
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shirobai
|
|
4
|
+
module Cop
|
|
5
|
+
module RSpec
|
|
6
|
+
# Drop-in Rust reimplementation of `RSpec/EmptyLineAfterHook`
|
|
7
|
+
# (rubocop-rspec 3.10.2).
|
|
8
|
+
#
|
|
9
|
+
# Flags every hook (`before`/`after`/`around`/... in ANY block form —
|
|
10
|
+
# plain, numbered, `it`-param) that has a following sibling in a `:begin`
|
|
11
|
+
# sequence, minus the allowed consecutive-one-liner hook chain
|
|
12
|
+
# (`AllowConsecutiveOneLiners`, default true).
|
|
13
|
+
class EmptyLineAfterHook < RuboCop::Cop::Base
|
|
14
|
+
extend RuboCop::Cop::AutoCorrector
|
|
15
|
+
include Shirobai::Cop::BundleEligible
|
|
16
|
+
include Shirobai::Cop::RSpec::EmptyLineSeparationSupport
|
|
17
|
+
|
|
18
|
+
MSG = "Add an empty line after `%<hook>s`."
|
|
19
|
+
|
|
20
|
+
def self.cop_name = "RSpec/EmptyLineAfterHook"
|
|
21
|
+
def self.badge = RuboCop::Cop::Badge.parse(cop_name)
|
|
22
|
+
|
|
23
|
+
# `AllowConsecutiveOneLiners` (default true) -> segment num.
|
|
24
|
+
def self.bundle_args(config)
|
|
25
|
+
[config.for_badge(badge).fetch("AllowConsecutiveOneLiners", true) ? 1 : 0]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def on_new_investigation
|
|
29
|
+
emit_empty_line_offenses(resolved_offenses) do |method|
|
|
30
|
+
format(MSG, hook: method)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def resolved_offenses
|
|
37
|
+
if bundle_eligible?
|
|
38
|
+
offenses = Dispatch.offenses_for(processed_source, config, :rspec_empty_line_after_hook)
|
|
39
|
+
return offenses unless offenses.nil?
|
|
40
|
+
end
|
|
41
|
+
Shirobai.check_rspec_empty_line_after_hook(
|
|
42
|
+
processed_source.buffer.source, *Shirobai::RSpec.segment(config)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|