forked from rubocop/rubocop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
frozen_string_literal_comment.rb
215 lines (184 loc) · 6.64 KB
/
frozen_string_literal_comment.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# Helps you transition from mutable string literals
# to frozen string literals.
# It will add the `# frozen_string_literal: true` magic comment to the top
# of files to enable frozen string literals. Frozen string literals may be
# default in future Ruby. The comment will be added below a shebang and
# encoding comment. The frozen string literal comment is only valid in Ruby 2.3+.
#
# Note that the cop will accept files where the comment exists but is set
# to `false` instead of `true`.
#
# To require a blank line after this comment, please see
# `Layout/EmptyLineAfterMagicComment` cop.
#
# @safety
# This cop's autocorrection is unsafe since any strings mutations will
# change from being accepted to raising `FrozenError`, as all strings
# will become frozen by default, and will need to be manually refactored.
#
# @example EnforcedStyle: always (default)
# # The `always` style will always add the frozen string literal comment
# # to a file, regardless of the Ruby version or if `freeze` or `<<` are
# # called on a string literal.
# # bad
# module Bar
# # ...
# end
#
# # good
# # frozen_string_literal: true
#
# module Bar
# # ...
# end
#
# # good
# # frozen_string_literal: false
#
# module Bar
# # ...
# end
#
# @example EnforcedStyle: never
# # The `never` will enforce that the frozen string literal comment does
# # not exist in a file.
# # bad
# # frozen_string_literal: true
#
# module Baz
# # ...
# end
#
# # good
# module Baz
# # ...
# end
#
# @example EnforcedStyle: always_true
# # The `always_true` style enforces that the frozen string literal
# # comment is set to `true`. This is a stricter option than `always`
# # and forces projects to use frozen string literals.
# # bad
# # frozen_string_literal: false
#
# module Baz
# # ...
# end
#
# # bad
# module Baz
# # ...
# end
#
# # good
# # frozen_string_literal: true
#
# module Bar
# # ...
# end
class FrozenStringLiteralComment < Base
include ConfigurableEnforcedStyle
include FrozenStringLiteral
include RangeHelp
extend AutoCorrector
MSG_MISSING_TRUE = 'Missing magic comment `# frozen_string_literal: true`.'
MSG_MISSING = 'Missing frozen string literal comment.'
MSG_UNNECESSARY = 'Unnecessary frozen string literal comment.'
MSG_DISABLED = 'Frozen string literal comment must be set to `true`.'
SHEBANG = '#!'
def on_new_investigation
return if processed_source.tokens.empty?
case style
when :never
ensure_no_comment(processed_source)
when :always_true
ensure_enabled_comment(processed_source)
else
ensure_comment(processed_source)
end
end
private
def ensure_no_comment(processed_source)
return unless frozen_string_literal_comment_exists?
unnecessary_comment_offense(processed_source)
end
def ensure_comment(processed_source)
return if frozen_string_literal_comment_exists?
missing_offense(processed_source)
end
def ensure_enabled_comment(processed_source)
if frozen_string_literal_specified?
return if frozen_string_literals_enabled?
# The comment exists, but is not enabled.
disabled_offense(processed_source)
else # The comment doesn't exist at all.
missing_true_offense(processed_source)
end
end
def last_special_comment(processed_source)
token_number = 0
if processed_source.tokens[token_number].text.start_with?(SHEBANG)
token = processed_source.tokens[token_number]
token_number += 1
end
next_token = processed_source.tokens[token_number]
token = next_token if Encoding::ENCODING_PATTERN.match?(next_token&.text)
token
end
def frozen_string_literal_comment(processed_source)
processed_source.find_token do |token|
token.text.start_with?(FROZEN_STRING_LITERAL)
end
end
def missing_offense(processed_source)
range = source_range(processed_source.buffer, 0, 0)
add_offense(range, message: MSG_MISSING) { |corrector| insert_comment(corrector) }
end
def missing_true_offense(processed_source)
range = source_range(processed_source.buffer, 0, 0)
add_offense(range, message: MSG_MISSING_TRUE) { |corrector| insert_comment(corrector) }
end
def unnecessary_comment_offense(processed_source)
frozen_string_literal_comment = frozen_string_literal_comment(processed_source)
add_offense(frozen_string_literal_comment.pos, message: MSG_UNNECESSARY) do |corrector|
remove_comment(corrector, frozen_string_literal_comment)
end
end
def disabled_offense(processed_source)
frozen_string_literal_comment = frozen_string_literal_comment(processed_source)
add_offense(frozen_string_literal_comment.pos, message: MSG_DISABLED) do |corrector|
enable_comment(corrector)
end
end
def remove_comment(corrector, node)
corrector.remove(range_with_surrounding_space(node.pos, side: :right))
end
def enable_comment(corrector)
comment = frozen_string_literal_comment(processed_source)
corrector.replace(line_range(comment.line), FROZEN_STRING_LITERAL_ENABLED)
end
def insert_comment(corrector)
comment = last_special_comment(processed_source)
if comment
corrector.insert_after(line_range(comment.line), following_comment)
else
corrector.insert_before(processed_source.buffer.source_range, preceding_comment)
end
end
def line_range(line)
processed_source.buffer.line_range(line)
end
def preceding_comment
"#{FROZEN_STRING_LITERAL_ENABLED}\n"
end
def following_comment
"\n#{FROZEN_STRING_LITERAL_ENABLED}"
end
end
end
end
end