forked from rubocop/rubocop
/
empty_literal.rb
138 lines (114 loc) · 4.32 KB
/
empty_literal.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
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop checks for the use of a method, the result of which
# would be a literal, like an empty array, hash, or string.
#
# @example
# # bad
# a = Array.new
# h = Hash.new
# s = String.new
#
# # good
# a = []
# h = {}
# s = ''
class EmptyLiteral < Base
include FrozenStringLiteral
include RangeHelp
extend AutoCorrector
ARR_MSG = 'Use array literal `[]` instead of `Array.new`.'
HASH_MSG = 'Use hash literal `{}` instead of `Hash.new`.'
STR_MSG = 'Use string literal `%<prefer>s` instead of `String.new`.'
RESTRICT_ON_SEND = %i[new].freeze
# @!method array_node(node)
def_node_matcher :array_node, '(send (const {nil? cbase} :Array) :new)'
# @!method hash_node(node)
def_node_matcher :hash_node, '(send (const {nil? cbase} :Hash) :new)'
# @!method str_node(node)
def_node_matcher :str_node, '(send (const {nil? cbase} :String) :new)'
# @!method array_with_block(node)
def_node_matcher :array_with_block, '(block (send (const {nil? cbase} :Array) :new) args _)'
# @!method hash_with_block(node)
def_node_matcher :hash_with_block, <<~PATTERN
{
(block (send (const {nil? cbase} :Hash) :new) args _)
(numblock (send (const {nil? cbase} :Hash) :new) ...)
}
PATTERN
def on_send(node)
return unless (message = offense_message(node))
add_offense(node, message: message) do |corrector|
corrector.replace(replacement_range(node), correction(node))
end
end
private
def offense_message(node)
if offense_array_node?(node)
ARR_MSG
elsif offense_hash_node?(node)
HASH_MSG
elsif str_node(node) && !frozen_strings?
format(STR_MSG, prefer: preferred_string_literal)
end
end
def preferred_string_literal
enforce_double_quotes? ? '""' : "''"
end
def enforce_double_quotes?
string_literals_config['EnforcedStyle'] == 'double_quotes'
end
def string_literals_config
config.for_cop('Style/StringLiterals')
end
def first_argument_unparenthesized?(node)
parent = node.parent
return false unless parent && %i[send super zsuper].include?(parent.type)
node.equal?(parent.arguments.first) && !parentheses?(node.parent)
end
def replacement_range(node)
if hash_node(node) && first_argument_unparenthesized?(node)
# `some_method {}` is not same as `some_method Hash.new`
# because the braces are interpreted as a block. We will have
# to rewrite the arguments to wrap them in parenthesis.
args = node.parent.arguments
range_between(args[0].source_range.begin_pos - 1, args[-1].source_range.end_pos)
else
node.source_range
end
end
def offense_array_node?(node)
array_node(node) && !array_with_block(node.parent)
end
def offense_hash_node?(node)
# If Hash.new takes a block, it can't be changed to {}.
hash_node(node) && !hash_with_block(node.parent)
end
def correction(node)
if offense_array_node?(node)
'[]'
elsif str_node(node)
preferred_string_literal
elsif offense_hash_node?(node)
if first_argument_unparenthesized?(node)
# `some_method {}` is not same as `some_method Hash.new`
# because the braces are interpreted as a block. We will have
# to rewrite the arguments to wrap them in parenthesis.
args = node.parent.arguments
"(#{args[1..-1].map(&:source).unshift('{}').join(', ')})"
else
'{}'
end
end
end
def frozen_strings?
return true if frozen_string_literals_enabled?
frozen_string_cop_enabled = config.for_cop('Style/FrozenStringLiteral')['Enabled']
frozen_string_cop_enabled && !frozen_string_literals_disabled?
end
end
end
end
end