forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
static_class.rb
128 lines (112 loc) · 3.47 KB
/
static_class.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
128
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Checks for places where classes with only class methods can be
# replaced with a module. Classes should be used only when it makes sense to create
# instances out of them.
#
# @safety
# This cop is unsafe, because it is possible that this class is a parent
# for some other subclass, monkey-patched with instance methods or
# a dummy instance is instantiated from it somewhere.
#
# @example
# # bad
# class SomeClass
# def self.some_method
# # body omitted
# end
#
# def self.some_other_method
# # body omitted
# end
# end
#
# # good
# module SomeModule
# module_function
#
# def some_method
# # body omitted
# end
#
# def some_other_method
# # body omitted
# end
# end
#
# # good - has instance method
# class SomeClass
# def instance_method; end
# def self.class_method; end
# end
#
class StaticClass < Base
include RangeHelp
include VisibilityHelp
extend AutoCorrector
MSG = 'Prefer modules to classes with only class methods.'
def on_class(class_node)
return if class_node.parent_class
return unless class_convertible_to_module?(class_node)
add_offense(class_node) do |corrector|
autocorrect(corrector, class_node)
end
end
private
def autocorrect(corrector, class_node)
corrector.replace(class_node.loc.keyword, 'module')
corrector.insert_after(class_node.loc.name, "\nmodule_function\n")
class_elements(class_node).each do |node|
if node.defs_type?
autocorrect_def(corrector, node)
elsif node.sclass_type?
autocorrect_sclass(corrector, node)
end
end
end
def autocorrect_def(corrector, node)
corrector.remove(
range_between(node.receiver.source_range.begin_pos, node.loc.name.begin_pos)
)
end
def autocorrect_sclass(corrector, node)
corrector.remove(
range_between(node.loc.keyword.begin_pos, node.identifier.source_range.end_pos)
)
corrector.remove(node.loc.end)
end
def class_convertible_to_module?(class_node)
nodes = class_elements(class_node)
return false if nodes.empty?
nodes.all? do |node|
(node_visibility(node) == :public && node.defs_type?) ||
sclass_convertible_to_module?(node) ||
node.equals_asgn? ||
extend_call?(node)
end
end
def extend_call?(node)
node.send_type? && node.method?(:extend)
end
def sclass_convertible_to_module?(node)
return false unless node.sclass_type?
class_elements(node).all? do |child|
node_visibility(child) == :public && (child.def_type? || child.equals_asgn?)
end
end
def class_elements(class_node)
class_def = class_node.body
if !class_def
[]
elsif class_def.begin_type?
class_def.children
else
[class_def]
end
end
end
end
end
end