rubocop-rspec_parity 1.3.2 → 1.3.4
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 +9 -0
- data/lib/rubocop/cop/rspec_parity/public_method_has_spec.rb +54 -6
- data/lib/rubocop/rspec_parity/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b634068a8fe93d9576c210988a5ebab48f22f25058cf7c50e21710de1b7fb775
|
|
4
|
+
data.tar.gz: 9ac6bd2fc8503e43013e25e03413cb32dd8eb3d6ee44480f1a243f41e006781f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab0f3101fd7603ec83438f91772b6991446c9fbb2102cc32ebb948a02df1cfe8a6217e71b2629ba695135b717322d9ba6bd686bfe1a9f58fbe292ea1b70357ba
|
|
7
|
+
data.tar.gz: d30491decd3a61710bf1c873bbd268aad7d5a40e6d209b46676bf469871c54087e13a6111a47ad83437d5becb6416e5075cec6ca6a0e00a7b7657c873bb44ce2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.4] - 2026-02-23
|
|
4
|
+
|
|
5
|
+
Added: `PublicMethodHasSpec` now treats methods inside `class_methods do` blocks (ActiveSupport::Concern) as class methods, expecting `.method_name` in specs
|
|
6
|
+
|
|
7
|
+
## [1.3.3] - 2026-02-20
|
|
8
|
+
|
|
9
|
+
Fixed: `PublicMethodHasSpec` offense message now shows correct method prefix (`#` for instance methods, `.` for class methods) and includes configured `DescribeAliases` in the expected describes
|
|
10
|
+
Fixed: `PublicMethodHasSpec` no longer reports methods in inner classes (class nested inside another class) as needing specs
|
|
11
|
+
|
|
3
12
|
## [1.3.2] - 2026-02-20
|
|
4
13
|
|
|
5
14
|
Fixed: `PublicMethodHasSpec` now recognizes specs that use module wrapping (e.g., `module Foo; RSpec.describe Bar`) instead of fully qualified class names
|
|
@@ -16,7 +16,7 @@ module RuboCop
|
|
|
16
16
|
include SpecFileFinder
|
|
17
17
|
|
|
18
18
|
MSG = "Missing spec for public method `%<method_name>s`. " \
|
|
19
|
-
"Expected
|
|
19
|
+
"Expected %<expected>s in %<spec_path>s"
|
|
20
20
|
|
|
21
21
|
COVERED_DIRECTORIES = %w[models controllers services jobs mailers helpers].freeze
|
|
22
22
|
EXCLUDED_METHODS = %w[initialize].freeze
|
|
@@ -32,13 +32,15 @@ module RuboCop
|
|
|
32
32
|
|
|
33
33
|
def on_def(node)
|
|
34
34
|
return unless checkable_method?(node) && public_method?(node)
|
|
35
|
+
return if inside_inner_class?(node)
|
|
35
36
|
|
|
36
|
-
check_method_has_spec(node, instance_method: !inside_eigenclass?(node))
|
|
37
|
+
check_method_has_spec(node, instance_method: !inside_eigenclass?(node) && !inside_class_methods_block?(node))
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def on_defs(node)
|
|
40
41
|
return unless checkable_method?(node) && public_class_method?(node)
|
|
41
42
|
return if EXCLUDED_HOOK_METHODS.include?(node.method_name.to_s)
|
|
43
|
+
return if inside_inner_class?(node)
|
|
42
44
|
|
|
43
45
|
check_method_has_spec(node, instance_method: false)
|
|
44
46
|
end
|
|
@@ -75,6 +77,36 @@ module RuboCop
|
|
|
75
77
|
node.each_ancestor.any? { |a| a.sclass_type? && a.children.first&.self_type? }
|
|
76
78
|
end
|
|
77
79
|
|
|
80
|
+
def inside_class_methods_block?(node)
|
|
81
|
+
node.each_ancestor(:block).any? do |block_node|
|
|
82
|
+
block_node.send_node.method_name == :class_methods
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def inside_inner_class?(node)
|
|
87
|
+
enclosing = find_class_or_module(node)
|
|
88
|
+
return false unless enclosing
|
|
89
|
+
|
|
90
|
+
enclosing.each_ancestor.any? { |a| a.class_type? && class_has_methods?(a) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def class_has_methods?(class_node)
|
|
94
|
+
return false unless class_node.body
|
|
95
|
+
|
|
96
|
+
children = class_node.body.begin_type? ? class_node.body.children : [class_node.body]
|
|
97
|
+
children.any? { |child| child.def_type? || child.defs_type? || eigenclass_with_methods?(child) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def eigenclass_with_methods?(node)
|
|
101
|
+
return false unless node.sclass_type? && node.children.first&.self_type?
|
|
102
|
+
|
|
103
|
+
body = node.body
|
|
104
|
+
return false unless body
|
|
105
|
+
|
|
106
|
+
body_children = body.begin_type? ? body.children : [body]
|
|
107
|
+
body_children.any?(&:def_type?)
|
|
108
|
+
end
|
|
109
|
+
|
|
78
110
|
def should_check_file?
|
|
79
111
|
path = processed_source.file_path
|
|
80
112
|
return false if path.nil? || !path.include?("/app/") || path.end_with?("_spec.rb")
|
|
@@ -101,7 +133,13 @@ module RuboCop
|
|
|
101
133
|
end
|
|
102
134
|
|
|
103
135
|
def find_enclosing_scope(node)
|
|
104
|
-
node.each_ancestor.find
|
|
136
|
+
node.each_ancestor.find do |n|
|
|
137
|
+
n.class_type? || n.module_type? || n.sclass_type? || class_methods_block?(n)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def class_methods_block?(node)
|
|
142
|
+
node.block_type? && node.send_node.method_name == :class_methods
|
|
105
143
|
end
|
|
106
144
|
|
|
107
145
|
def find_class_or_module(node)
|
|
@@ -175,7 +213,7 @@ module RuboCop
|
|
|
175
213
|
return
|
|
176
214
|
end
|
|
177
215
|
|
|
178
|
-
add_method_offense(node, method_name, spec_paths.first)
|
|
216
|
+
add_method_offense(node, method_name, spec_paths.first, instance_method: instance_method)
|
|
179
217
|
end
|
|
180
218
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
181
219
|
|
|
@@ -191,13 +229,23 @@ module RuboCop
|
|
|
191
229
|
end
|
|
192
230
|
end
|
|
193
231
|
|
|
194
|
-
def add_method_offense(node, method_name, spec_path)
|
|
232
|
+
def add_method_offense(node, method_name, spec_path, instance_method:)
|
|
233
|
+
prefix = instance_method ? "#" : "."
|
|
234
|
+
expected = expected_describes(prefix, method_name)
|
|
195
235
|
add_offense(
|
|
196
236
|
node.loc.keyword.join(node.loc.name),
|
|
197
|
-
message: format(MSG, method_name: method_name, spec_path: relative_spec_path(spec_path))
|
|
237
|
+
message: format(MSG, method_name: method_name, expected: expected, spec_path: relative_spec_path(spec_path))
|
|
198
238
|
)
|
|
199
239
|
end
|
|
200
240
|
|
|
241
|
+
def expected_describes(prefix, method_name)
|
|
242
|
+
describes = ["describe '#{prefix}#{method_name}'"]
|
|
243
|
+
describe_aliases_for("#{prefix}#{method_name}").each do |alias_desc|
|
|
244
|
+
describes << "describe '#{alias_desc}'"
|
|
245
|
+
end
|
|
246
|
+
describes.join(" or ")
|
|
247
|
+
end
|
|
248
|
+
|
|
201
249
|
def method_tested_in_spec?(spec_path, method_name, instance_method)
|
|
202
250
|
spec_content = File.read(spec_path)
|
|
203
251
|
prefix = instance_method ? "#" : "."
|