forked from rubocop/rubocop
/
redundant_percent_q.rb
110 lines (91 loc) · 3.15 KB
/
redundant_percent_q.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
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop checks for usage of the %q/%Q syntax when '' or "" would do.
#
# @example
#
# # bad
# name = %q(Bruce Wayne)
# time = %q(8 o'clock)
# question = %q("What did you say?")
#
# # good
# name = 'Bruce Wayne'
# time = "8 o'clock"
# question = '"What did you say?"'
#
class RedundantPercentQ < Base
extend AutoCorrector
MSG = 'Use `%<q_type>s` only for strings that contain both ' \
'single quotes and double quotes%<extra>s.'
DYNAMIC_MSG = ', or for dynamic strings that contain ' \
'double quotes'
SINGLE_QUOTE = "'"
QUOTE = '"'
EMPTY = ''
PERCENT_Q = '%q'
PERCENT_CAPITAL_Q = '%Q'
STRING_INTERPOLATION_REGEXP = /#\{.+\}/.freeze
ESCAPED_NON_BACKSLASH = /\\[^\\]/.freeze
def on_dstr(node)
return unless string_literal?(node)
check(node)
end
def on_str(node)
# Interpolated strings that contain more than just interpolation
# will call `on_dstr` for the entire string and `on_str` for the
# non interpolated portion of the string
return unless string_literal?(node)
check(node)
end
private
def check(node)
return unless start_with_percent_q_variant?(node)
return if interpolated_quotes?(node) || allowed_percent_q?(node)
add_offense(node) do |corrector|
delimiter = /^%Q[^"]+$|'/.match?(node.source) ? QUOTE : SINGLE_QUOTE
corrector.replace(node.loc.begin, delimiter)
corrector.replace(node.loc.end, delimiter)
end
end
def interpolated_quotes?(node)
node.source.include?(SINGLE_QUOTE) && node.source.include?(QUOTE)
end
def allowed_percent_q?(node)
node.source.start_with?(PERCENT_Q) && acceptable_q?(node) ||
node.source.start_with?(PERCENT_CAPITAL_Q) &&
acceptable_capital_q?(node)
end
def message(node)
src = node.source
extra = if src.start_with?(PERCENT_CAPITAL_Q)
DYNAMIC_MSG
else
EMPTY
end
format(MSG, q_type: src[0, 2], extra: extra)
end
def string_literal?(node)
node.loc.respond_to?(:begin) && node.loc.respond_to?(:end) &&
node.loc.begin && node.loc.end
end
def start_with_percent_q_variant?(string)
string.source.start_with?(PERCENT_Q, PERCENT_CAPITAL_Q)
end
def acceptable_q?(node)
src = node.source
return true if STRING_INTERPOLATION_REGEXP.match?(src)
src.scan(/\\./).any? { |s| ESCAPED_NON_BACKSLASH.match?(s) }
end
def acceptable_capital_q?(node)
src = node.source
src.include?(QUOTE) &&
(STRING_INTERPOLATION_REGEXP.match?(src) ||
(node.str_type? && double_quotes_required?(src)))
end
end
end
end
end