lib/rubocop/cop/rails/dot_separated_keys.rb
# frozen_string_literal: true module RuboCop module Cop module Rails # Enforces the use of dot-separated locale keys instead of specifying the `:scope` option # with an array or a single symbol in `I18n` translation methods. # Dot-separated notation is easier to read and trace the hierarchy. # # @example # # bad # I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] # I18n.t :title, scope: :invitation # # # good # I18n.t 'activerecord.errors.messages.record_invalid' # I18n.t :record_invalid, scope: 'activerecord.errors.messages' # class DotSeparatedKeys < Base include RangeHelp extend AutoCorrector MSG = 'Use the dot-separated keys instead of specifying the `:scope` option.' TRANSLATE_METHODS = %i[translate t].freeze def_node_matcher :translate_with_scope?, <<~PATTERN (send {nil? (const nil? :I18n)} {:translate :t} ${sym_type? str_type?} (hash <$(pair (sym :scope) ${array_type? sym_type?}) ...>) ) PATTERN def on_send(node) return unless TRANSLATE_METHODS.include?(node.method_name) translate_with_scope?(node) do |key_node, scope_node| return unless should_convert_scope?(scope_node) add_offense(scope_node) do |corrector| # Eat the comma on the left. range = range_with_surrounding_space(range: scope_node.source_range, side: :left) range = range_with_surrounding_comma(range, :left) corrector.remove(range) corrector.replace(key_node, new_key(key_node, scope_node)) end end end private def should_convert_scope?(scope_node) scopes(scope_node).all?(&:basic_literal?) end def new_key(key_node, scope_node) "'#{scopes(scope_node).map(&:value).join('.')}.#{key_node.value}'".squeeze('.') end def scopes(scope_node) value = scope_node.value if value.array_type? value.values else [value] end end end end end end