lib/rubocop/cop/rails/i18n_lazy_lookup.rb
# 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