forked from puppetlabs/rubocop-i18n
/
decorate_function_message.rb
156 lines (127 loc) · 5.69 KB
/
decorate_function_message.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 I18n
module GetText
class DecorateFunctionMessage < Base
extend AutoCorrector
def on_send(node)
method_name = node.loc.selector.source
return unless GetText.supported_method?(method_name)
method_name = node.method_name
arg_nodes = node.arguments
if !arg_nodes.empty? && !already_decorated?(node) && (contains_string?(arg_nodes) || string_constant?(arg_nodes))
message_section = if string_constant?(arg_nodes)
arg_nodes[1]
else
arg_nodes[0]
end
detect_and_report(node, message_section, method_name)
end
end
private
def already_decorated?(node, parent = nil)
parent ||= node
if node.respond_to?(:loc) && node.loc.respond_to?(:selector)
return true if GetText.supported_decorator?(node.loc.selector.source)
end
return false unless node.respond_to?(:children)
node.children.any? { |child| already_decorated?(child, parent) }
end
def string_constant?(nodes)
nodes[0].const_type? && nodes[1]
end
def contains_string?(nodes)
nodes[0].inspect.include?(':str') || nodes[0].inspect.include?(':dstr')
end
def detect_and_report(_node, message_section, method_name)
errors = how_bad_is_it(message_section)
return if errors.empty?
error_message = ["'#{method_name}' function, "]
errors.each do |error|
error_message << 'message string should be decorated. ' if error == :simple
error_message << 'message should not be a concatenated string. ' if error == :concatenation
error_message << 'message should not be a multi-line string. ' if error == :multiline
error_message << 'message should use correctly formatted interpolation. ' if error == :interpolation
error_message << 'message should be decorated. ' if error == :no_decoration
end
error_message = error_message.join('\n')
add_offense(message_section, message: error_message) do |corrector|
autocorrect(corrector, message_section)
end
end
def autocorrect(corrector, node)
if node.str_type?
single_string_correct(corrector, node)
elsif interpolation_offense?(node)
# interpolation_correct(node)
end
end
def how_bad_is_it(message_section)
errors = []
errors.push :simple if message_section.str_type?
errors.push :multiline if message_section.multiline?
errors.push :concatenation if concatenation_offense?(message_section)
errors.push :interpolation if interpolation_offense?(message_section)
errors.push :no_decoration unless already_decorated?(message_section)
# only display no_decoration, if that is the only problem.
if errors.size > 1 && errors.include?(:no_decoration)
errors.delete(:no_decoration)
end
errors
end
def concatenation_offense?(node, parent = nil)
parent ||= node
if node.respond_to?(:loc) && node.loc.respond_to?(:selector)
return true if node.loc.selector.source == '+'
end
return false unless node.respond_to?(:children)
node.children.any? { |child| concatenation_offense?(child, parent) }
end
def interpolation_offense?(node, parent = nil)
parent ||= node
return true if node.respond_to?(:dstr_type?) && node.dstr_type?
return false unless node.respond_to?(:children)
node.children.any? { |child| interpolation_offense?(child, parent) }
end
def single_string_correct(corrector, node)
corrector.wrap(node.source_range, '_(', ')')
end
def interpolation_correct(node)
interpolated_values_string = ''
count = 0
lambda { |corrector|
node.children.each do |child|
# dstrs are split into "str" segments and other segments.
# The "other" segments are the interpolated values.
next unless child.begin_type?
value = child.children[0]
hash_key = 'value'
if value.lvar_type?
# Use the variable's name as the format key
hash_key = value.loc.name.source
else
# These are placeholders that will manually need to be given
# a descriptive name
hash_key << count.to_s
count += 1
end
if interpolated_values_string.empty?
interpolated_values_string << '{ '
end
interpolated_values_string << "#{hash_key}: #{value.loc.expression.source}, "
# Replace interpolation with format string
corrector.replace(child.loc.expression, "%{#{hash_key}}")
end
unless interpolated_values_string.empty?
interpolated_values_string << '}'
end
corrector.insert_before(node.source_range, '_(')
corrector.insert_after(node.source_range, ") % #{interpolated_values_string}")
}
end
end
end
end
end
end