-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
alias.rb
156 lines (134 loc) · 5.08 KB
/
alias.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
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Enforces the use of either `#alias` or `#alias_method`
# depending on configuration.
# It also flags uses of `alias :symbol` rather than `alias bareword`.
#
# However, it will always enforce `method_alias` when used `alias`
# in an instance method definition and in a singleton method definition.
# If used in a block, always enforce `alias_method`
# unless it is an `instance_eval` block.
#
# @example EnforcedStyle: prefer_alias (default)
# # bad
# alias_method :bar, :foo
# alias :bar :foo
#
# # good
# alias bar foo
#
# @example EnforcedStyle: prefer_alias_method
# # bad
# alias :bar :foo
# alias bar foo
#
# # good
# alias_method :bar, :foo
#
class Alias < Base
include ConfigurableEnforcedStyle
extend AutoCorrector
MSG_ALIAS = 'Use `alias_method` instead of `alias`.'
MSG_ALIAS_METHOD = 'Use `alias` instead of `alias_method` %<current>s.'
MSG_SYMBOL_ARGS = 'Use `alias %<prefer>s` instead of `alias %<current>s`.'
RESTRICT_ON_SEND = %i[alias_method].freeze
def on_send(node)
return unless node.command?(:alias_method)
return unless style == :prefer_alias && alias_keyword_possible?(node)
msg = format(MSG_ALIAS_METHOD, current: lexical_scope_type(node))
add_offense(node.loc.selector, message: msg) do |corrector|
autocorrect(corrector, node)
end
end
def on_alias(node)
return unless alias_method_possible?(node)
if scope_type(node) == :dynamic || style == :prefer_alias_method
add_offense(node.loc.keyword, message: MSG_ALIAS) do |corrector|
autocorrect(corrector, node)
end
elsif node.children.none? { |arg| bareword?(arg) }
add_offense_for_args(node) { |corrector| autocorrect(corrector, node) }
end
end
private
def autocorrect(corrector, node)
if node.send_type?
correct_alias_method_to_alias(corrector, node)
elsif scope_type(node) == :dynamic || style == :prefer_alias_method
correct_alias_to_alias_method(corrector, node)
else
correct_alias_with_symbol_args(corrector, node)
end
end
def alias_keyword_possible?(node)
scope_type(node) != :dynamic && node.arguments.all?(&:sym_type?)
end
def alias_method_possible?(node)
scope_type(node) != :instance_eval &&
node.children.none?(&:gvar_type?) &&
node&.parent&.type != :def
end
def add_offense_for_args(node, &block)
existing_args = node.children.map(&:source).join(' ')
preferred_args = node.children.map { |a| a.source[1..] }.join(' ')
arg_ranges = node.children.map(&:source_range)
msg = format(MSG_SYMBOL_ARGS, prefer: preferred_args, current: existing_args)
add_offense(arg_ranges.reduce(&:join), message: msg, &block)
end
# In this expression, will `self` be the same as the innermost enclosing
# class or module block (:lexical)? Or will it be something else
# (:dynamic)? If we're in an instance_eval block, return that.
def scope_type(node)
while (parent = node.parent)
case parent.type
when :class, :module
return :lexical
when :def, :defs
return :dynamic
when :block
return :instance_eval if parent.method?(:instance_eval)
return :dynamic
end
node = parent
end
:lexical
end
def lexical_scope_type(node)
ancestor = node.each_ancestor(:class, :module).first
if ancestor.nil?
'at the top level'
elsif ancestor.class_type?
'in a class body'
else
'in a module body'
end
end
def bareword?(sym_node)
!sym_node.source.start_with?(':')
end
def correct_alias_method_to_alias(corrector, send_node)
new, old = *send_node.arguments
replacement = "alias #{identifier(new)} #{identifier(old)}"
corrector.replace(send_node, replacement)
end
def correct_alias_to_alias_method(corrector, node)
replacement =
'alias_method ' \
":#{identifier(node.new_identifier)}, " \
":#{identifier(node.old_identifier)}"
corrector.replace(node, replacement)
end
def correct_alias_with_symbol_args(corrector, node)
corrector.replace(node.new_identifier, node.new_identifier.source[1..])
corrector.replace(node.old_identifier, node.old_identifier.source[1..])
end
# @!method identifier(node)
def_node_matcher :identifier, <<~PATTERN
(sym $_)
PATTERN
end
end
end
end