class ERBLint::Linters::HardCodedString

Checks for hardcoded strings. Useful if you want to ensure a string can be translated using i18n.

def autocorrect(processed_source, offense)

def autocorrect(processed_source, offense)
  string = offense.source_range.source
  return unless (klass = load_corrector)
  return if string.strip.length <= 1
  node = ::RuboCop::AST::StrNode.new(:str, [string])
  corrector = klass.new(node, processed_source.filename, corrector_i18n_load_path, offense.source_range)
  corrector.autocorrect(tag_start: "<%= ", tag_end: " %>")
rescue MissingCorrector, MissingI18nLoadPath
  nil
end

def check_string?(str)

def check_string?(str)
  string = str.gsub(/\s*/, "")
  string.length > 1 && !NO_TRANSLATION_NEEDED.include?(string)
end

def corrector_i18n_load_path

def corrector_i18n_load_path
  @config["corrector"].fetch("i18n_load_path") { raise MissingI18nLoadPath }
end

def find_range(node, str)

def find_range(node, str)
  match = node.loc.source.match(Regexp.new(Regexp.quote(str.strip)))
  return unless match
  range_begin = match.begin(0) + node.loc.begin_pos
  range_end   = match.end(0) + node.loc.begin_pos
  (range_begin...range_end)
end

def load_corrector

def load_corrector
  corrector_name = @config["corrector"].fetch("name") { raise MissingCorrector }
  raise ForbiddenCorrector unless ALLOWED_CORRECTORS.include?(corrector_name)
  require @config["corrector"].fetch("path") { raise MissingCorrector }
  corrector_name.safe_constantize
end

def message(string)

def message(string)
  stripped_string = string.strip
  "String not translated: #{stripped_string}"
end

def non_text_tag?(processed_source, text_node)

def non_text_tag?(processed_source, text_node)
  ast = processed_source.parser.ast.to_a
  index = ast.find_index(text_node)
  previous_node = ast[index - 1]
  if previous_node.type == :tag
    tag = BetterHtml::Tree::Tag.from_node(previous_node)
    NON_TEXT_TAGS.include?(tag.name) && !tag.closing?
  end
end

def relevant_node(inner_node)

def relevant_node(inner_node)
  if inner_node.is_a?(String)
    inner_node.strip.empty? ? false : inner_node
  else
    false
  end
end

def run(processed_source)

def run(processed_source)
  hardcoded_strings = processed_source.ast.descendants(:text).each_with_object([]) do |text_node, to_check|
    next if non_text_tag?(processed_source, text_node)
    offended_strings = text_node.to_a.select { |node| relevant_node(node) }
    offended_strings.each do |offended_string|
      offended_string.split("\n").each do |str|
        to_check << [text_node, str] if check_string?(str)
      end
    end
  end
  hardcoded_strings.compact.each do |text_node, offended_str|
    range = find_range(text_node, offended_str)
    source_range = processed_source.to_source_range(range)
    add_offense(
      source_range,
      message(source_range.source),
    )
  end
end