Class: RuboCop::Cop::Neeto::HtmlTranslationInterpolation

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/neeto/html_translation_interpolation.rb

Overview

I18n.t does not escape interpolated values, even for _html keys (only the view helper t does). A translation that contains HTML and interpolates an unescaped value outside a view lets that value inject markup. The cop reads the translations from LocaleFiles.

Examples:

HtmlTranslationInterpolation: true (default)

# en.yml
#   seed:
#     description: "<p>Schedule a meeting with %{name}</p>"

# bad
I18n.t("seed.description", name: user.name)

# good
I18n.t("seed.description", name: ERB::Util.html_escape(user.name))

# good
tag.p(I18n.t("seed.description_text", name: user.name))

Constant Summary collapse

MSG =
"Translation `%<key>s` contains HTML and interpolates %<variables>s, which `I18n.t` does not " \
"escape. Escape each value with `ERB::Util.html_escape`, or keep the markup out of the translation " \
"and build it with `tag` or `content_tag`."
RESTRICT_ON_SEND =
%i[t translate].freeze
OPTIONS =
%i[locale scope default raise throw count].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.translations_cache ⇒ Object



43
44
45
# File 'lib/rubocop/cop/neeto/html_translation_interpolation.rb', line 43

def self.translations_cache
  @_translations_cache ||= {}
end

Instance Method Details

#on_send(node) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/rubocop/cop/neeto/html_translation_interpolation.rb', line 47

def on_send(node)
  translation_call(node) do |key, arguments|
    options = arguments.last
    next unless options&.hash_type?

    translation = translations[key]
    next unless translation.is_a?(String) && HtmlInStringLiteral::HTML_TAG.match?(translation)

    variables = unescaped_variables(options).select { |name| translation.include?("%{#{name}}") }
    next if variables.empty?

    add_offense(node, message: format(MSG, key:, variables: variables.map { |name| "%{#{name}}" }.join(", ")))
  end
end