/
i18n_lazy_lookup.rb
146 lines (124 loc) · 4.24 KB
/
i18n_lazy_lookup.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
# frozen_string_literal: true
module RuboCop
module Cop
module Rails
# Checks for places where I18n "lazy" lookup can be used.
#
# This cop has two different enforcement modes. When the EnforcedStyle
# is `lazy` (the default), explicit lookups are added as offenses.
#
# When the EnforcedStyle is `explicit` then lazy lookups are added as
# offenses.
#
# @example EnforcedStyle: lazy (default)
# # en.yml
# # en:
# # books:
# # create:
# # success: Book created!
#
# # bad
# class BooksController < ApplicationController
# def create
# # ...
# redirect_to books_url, notice: t('books.create.success')
# end
# end
#
# # good
# class BooksController < ApplicationController
# def create
# # ...
# redirect_to books_url, notice: t('.success')
# end
# end
#
# @example EnforcedStyle: explicit
# # bad
# class BooksController < ApplicationController
# def create
# # ...
# redirect_to books_url, notice: t('.success')
# end
# end
#
# # good
# class BooksController < ApplicationController
# def create
# # ...
# redirect_to books_url, notice: t('books.create.success')
# end
# end
#
class I18nLazyLookup < Base
include ConfigurableEnforcedStyle
include VisibilityHelp
extend AutoCorrector
MSG = 'Use %<style>s lookup for the text used in controllers.'
RESTRICT_ON_SEND = %i[translate t].freeze
def_node_matcher :translate_call?, <<~PATTERN
(send nil? {:translate :t} ${sym_type? str_type?} ...)
PATTERN
def on_send(node)
translate_call?(node) do |key_node|
case style
when :lazy
handle_lazy_style(node, key_node)
when :explicit
handle_explicit_style(node, key_node)
end
end
end
private
def handle_lazy_style(node, key_node)
key = key_node.value
return if key.to_s.start_with?('.')
controller, action = controller_and_action(node)
return unless controller && action
scoped_key = get_scoped_key(key_node, controller, action)
return unless key == scoped_key
add_offense(key_node) do |corrector|
unscoped_key = key_node.value.to_s.split('.').last
corrector.replace(key_node, "'.#{unscoped_key}'")
end
end
def handle_explicit_style(node, key_node)
key = key_node.value
return unless key.to_s.start_with?('.')
controller, action = controller_and_action(node)
return unless controller && action
scoped_key = get_scoped_key(key_node, controller, action)
add_offense(key_node) do |corrector|
corrector.replace(key_node, "'#{scoped_key}'")
end
end
def controller_and_action(node)
action_node = node.each_ancestor(:def).first
return unless action_node && node_visibility(action_node) == :public
controller_node = node.each_ancestor(:class).first
return unless controller_node && controller_node.identifier.source.end_with?('Controller')
[controller_node, action_node]
end
def get_scoped_key(key_node, controller, action)
path = controller_path(controller).tr('/', '.')
action_name = action.method_name
key = key_node.value.to_s.split('.').last
"#{path}.#{action_name}.#{key}"
end
def controller_path(controller)
module_name = controller.parent_module_name
controller_name = controller.identifier.source
path = if module_name == 'Object'
controller_name
else
"#{module_name}::#{controller_name}"
end
path.delete_suffix('Controller').underscore
end
def message(_range)
format(MSG, style: style)
end
end
end
end
end