/
class_methods_definitions.rb
157 lines (135 loc) · 3.98 KB
/
class_methods_definitions.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop enforces using `def self.method_name` or `class << self` to define class methods.
#
# @example EnforcedStyle: def_self (default)
# # bad
# class SomeClass
# class << self
# attr_accessor :class_accessor
#
# def class_method
# # ...
# end
# end
# end
#
# # good
# class SomeClass
# def self.class_method
# # ...
# end
#
# class << self
# attr_accessor :class_accessor
# end
# end
#
# # good - contains private method
# class SomeClass
# class << self
# attr_accessor :class_accessor
#
# private
#
# def private_class_method
# # ...
# end
# end
# end
#
# @example EnforcedStyle: self_class
# # bad
# class SomeClass
# def self.class_method
# # ...
# end
# end
#
# # good
# class SomeClass
# class << self
# def class_method
# # ...
# end
# end
# end
#
class ClassMethodsDefinitions < Base
include ConfigurableEnforcedStyle
include CommentsHelp
include VisibilityHelp
include RangeHelp
extend AutoCorrector
MSG = 'Use `%<preferred>s` to define a class method.'
MSG_SCLASS = 'Do not define public methods within class << self.'
def on_sclass(node)
return unless def_self_style?
return unless node.identifier.source == 'self'
return unless all_methods_public?(node)
add_offense(node, message: MSG_SCLASS) do |corrector|
autocorrect_sclass(node, corrector)
end
end
def on_defs(node)
return if def_self_style?
message = format(MSG, preferred: 'class << self')
add_offense(node, message: message)
end
private
def def_self_style?
style == :def_self
end
def all_methods_public?(sclass_node)
def_nodes = def_nodes(sclass_node)
return false if def_nodes.empty?
def_nodes.all? { |def_node| node_visibility(def_node) == :public }
end
def def_nodes(sclass_node)
sclass_def = sclass_node.body
return [] unless sclass_def
if sclass_def.def_type?
[sclass_def]
elsif sclass_def.begin_type?
sclass_def.each_child_node(:def).to_a
else
[]
end
end
def autocorrect_sclass(node, corrector)
rewritten_defs = []
def_nodes(node).each do |def_node|
next unless node_visibility(def_node) == :public
range, source = extract_def_from_sclass(def_node, node)
corrector.remove(range)
rewritten_defs << source
end
if sclass_only_has_methods?(node)
corrector.remove(node)
rewritten_defs.first&.strip!
else
corrector.insert_after(node, "\n")
end
corrector.insert_after(node, rewritten_defs.join("\n"))
end
def sclass_only_has_methods?(node)
node.body.def_type? || node.body.each_child_node.all?(&:def_type?)
end
def extract_def_from_sclass(def_node, sclass_node)
range = source_range_with_comment(def_node)
source = range.source.sub!(
"def #{def_node.method_name}",
"def self.#{def_node.method_name}"
)
source = source.gsub(/^ {#{indentation_diff(def_node, sclass_node)}}/, '')
[range, source.chomp]
end
def indentation_diff(node1, node2)
node1.loc.column - node2.loc.column
end
end
end
end
end