forked from rubocop/rubocop-rspec
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathleaky_constant_declaration.rb
127 lines (118 loc) · 3.71 KB
/
leaky_constant_declaration.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks that no class, module, or constant is declared.
#
# Constants, including classes and modules, when declared in a block
# scope, are defined in global namespace, and leak between examples.
#
# If several examples may define a `DummyClass`, instead of being a
# blank slate class as it will be in the first example, subsequent
# examples will be reopening it and modifying its behavior in
# unpredictable ways.
# Even worse when a class that exists in the codebase is reopened.
#
# Anonymous classes are fine, since they don't result in global
# namespace name clashes.
#
# @see https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants
#
# @example Constants leak between examples
# # bad
# describe SomeClass do
# OtherClass = Struct.new
# CONSTANT_HERE = 'I leak into global namespace'
# end
#
# # good
# describe SomeClass do
# before do
# stub_const('OtherClass', Struct.new)
# stub_const('CONSTANT_HERE', 'I only exist during this example')
# end
# end
#
# @example
# # bad
# describe SomeClass do
# class FooClass < described_class
# def double_that
# some_base_method * 2
# end
# end
#
# it { expect(FooClass.new.double_that).to eq(4) }
# end
#
# # good - anonymous class, no constant needs to be defined
# describe SomeClass do
# let(:foo_class) do
# Class.new(described_class) do
# def double_that
# some_base_method * 2
# end
# end
# end
#
# it { expect(foo_class.new.double_that).to eq(4) }
# end
#
# # good - constant is stubbed
# describe SomeClass do
# before do
# foo_class = Class.new(described_class) do
# def do_something
# end
# end
# stub_const('FooClass', foo_class)
# end
#
# it { expect(FooClass.new.double_that).to eq(4) }
# end
#
# @example
# # bad
# describe SomeClass do
# module SomeModule
# class SomeClass
# def do_something
# end
# end
# end
# end
#
# # good
# describe SomeClass do
# before do
# foo_class = Class.new(described_class) do
# def do_something
# end
# end
# stub_const('SomeModule::SomeClass', foo_class)
# end
# end
class LeakyConstantDeclaration < Base
MSG_CONST = 'Stub constant instead of declaring explicitly.'
MSG_CLASS = 'Stub class constant instead of declaring explicitly.'
MSG_MODULE = 'Stub module constant instead of declaring explicitly.'
def on_casgn(node)
return unless inside_describe_block?(node)
add_offense(node, message: MSG_CONST)
end
def on_class(node)
return unless inside_describe_block?(node)
add_offense(node, message: MSG_CLASS)
end
def on_module(node)
return unless inside_describe_block?(node)
add_offense(node, message: MSG_MODULE)
end
private
def inside_describe_block?(node)
node.each_ancestor(:block).any?(&method(:spec_group?))
end
end
end
end
end