diff options
Diffstat (limited to 'rubocop/cop/gettext/static_identifier.rb')
-rw-r--r-- | rubocop/cop/gettext/static_identifier.rb | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/rubocop/cop/gettext/static_identifier.rb b/rubocop/cop/gettext/static_identifier.rb new file mode 100644 index 00000000000..9ca1c88f4b6 --- /dev/null +++ b/rubocop/cop/gettext/static_identifier.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Gettext + # Ensure that gettext identifiers are statically defined and not + # interpolated, formatted, or concatenated. + # + # @example + # + # # bad + # _('Hi #{name}') + # _('Hi %{name}' % { name: 'Luki' }) + # _(format('Hi %{name}', name: 'Luki')) + # + # # good + # _('Hi %{name}') % { name: 'Luki' } + # format(_('Hi %{name}', name: 'Luki')) + # + # # also good + # var = "Hi" + # _(var) + # _(some_method_call) + # _(CONST) + class StaticIdentifier < RuboCop::Cop::Base + MSG = 'Ensure to pass static strings to translation method `%{method_name}(...)`.' + + # Number of parameters to check for translation methods. + PARAMETERS_TO_CHECK = { + _: 1, + s_: 1, + N_: 1, + n_: 2 + }.freeze + + # RuboCop-specific optimization for `on_send`. + RESTRICT_ON_SEND = PARAMETERS_TO_CHECK.keys.freeze + + DENIED_METHOD_CALLS = %i[% format + concat].freeze + + def on_send(node) + method_name = node.method_name + arguments = node.arguments + + each_invalid_argument(method_name, arguments) do |argument_node| + message = format(MSG, method_name: method_name) + + add_offense(argument_node || node, message: message) + end + end + + private + + def each_invalid_argument(method_name, argument_nodes) + number = PARAMETERS_TO_CHECK.fetch(method_name) + + argument_nodes.take(number).each do |argument_node| + yield argument_node unless valid_argument?(argument_node) + end + end + + def valid_argument?(node) + return false unless node + + basic_type?(node) || multiline_string?(node) || allowed_method_call?(node) + end + + def basic_type?(node) + node.str_type? || node.lvar_type? || node.const_type? + end + + def multiline_string?(node) + node.dstr_type? && node.children.all?(&:str_type?) + end + + def allowed_method_call?(node) + return false unless node.send_type? + + !DENIED_METHOD_CALLS.include?(node.method_name) # rubocop:disable Rails/NegateInclude + end + end + end + end +end |