<commit_before>class PyLintAdapter < TestingFrameworkAdapter
  REGEXP = /Your code has been rated at (-?\d+\.?\d*)\/(\d+\.?\d*)/
  ASSERTION_ERROR_REGEXP = /^(.*?\.py):(\d+):(.*?)\(([^,]*?), ([^,]*?),([^,]*?)\) (.*?)$/

  def self.framework_name
    'PyLint'
  end

  def parse_output(output)
    regex_match = REGEXP.match(output[:stdout])
    if regex_match.blank?
      count = 0
      failed = 0
    else
      captures = regex_match.captures.map(&:to_f)
      count = captures.second
      passed = captures.first >= 0 ? captures.first : 0
      failed = count - passed
    end

    begin
      assertion_error_matches = Timeout.timeout(2.seconds) do
        output[:stdout].scan(ASSERTION_ERROR_REGEXP).map do |match|
          {
            file_name: match[0].strip,
            line: match[1].to_i,
            severity: match[2].strip,
            code: match[3].strip,
            name: match[4].strip,
            # e.g. function name, nil if outside of a function. Not always available
            scope: match[5].strip.presence,
            result: match[6].strip
          }
        end || []
      end
    rescue Timeout::Error
      assertion_error_matches = []
    end
    concatenated_errors = assertion_error_matches.map { |result| "#{result[:name]}: #{result[:result]}" }.flatten
    {count: count, failed: failed, error_messages: concatenated_errors, detailed_linter_results: assertion_error_matches}
  end

  def self.translate_linter(assessment)
    # The message will be translated once the results were stored in the database
    # See SubmissionScoring for actual function call

    return assessment unless assessment[:detailed_linter_results].present?

    assessment[:detailed_linter_results].map! do |message|
      severity = message[:severity]
      name = message[:name]

      message[:severity] = I18n.t("linter.#{severity}.severity_name", locale: :de, default: message[:severity])
      message[:name] = I18n.t("linter.#{severity}.#{name}.name", locale: :de, default: message[:name])

      regex = I18n.t("linter.#{severity}.#{name}.regex", locale: :de, default: nil)&.strip

      if regex.present?
        captures = message[:result].match(Regexp.new(regex)).named_captures.symbolize_keys

        replacement = captures.each do |key, value|
          value&.replace I18n.t("linter.#{severity}.#{name}.#{key}.#{value}", default: value, locale: :de)
        end
      else
        replacement = {}
      end

      replacement.merge!(locale: :de, default: message[:result])
      message[:result] = I18n.t("linter.#{severity}.#{name}.replacement", replacement)
      message
    end

    assessment[:error_messages] = assessment[:detailed_linter_results].map do |message|
      "#{message[:name]}: #{message[:result]}"
    end

    assessment
  rescue StandardError => e
    # A key was not defined or something really bad happened
    Raven.extra_context(assessment)
    Raven.capture_exception(e)
    assessment
  end
end<commit_msg>Log unknown PyLint values to Sentry for translation<commit_after>class PyLintAdapter < TestingFrameworkAdapter
  REGEXP = /Your code has been rated at (-?\d+\.?\d*)\/(\d+\.?\d*)/
  ASSERTION_ERROR_REGEXP = /^(.*?\.py):(\d+):(.*?)\(([^,]*?), ([^,]*?),([^,]*?)\) (.*?)$/

  def self.framework_name
    'PyLint'
  end

  def parse_output(output)
    regex_match = REGEXP.match(output[:stdout])
    if regex_match.blank?
      count = 0
      failed = 0
    else
      captures = regex_match.captures.map(&:to_f)
      count = captures.second
      passed = captures.first >= 0 ? captures.first : 0
      failed = count - passed
    end

    begin
      assertion_error_matches = Timeout.timeout(2.seconds) do
        output[:stdout].scan(ASSERTION_ERROR_REGEXP).map do |match|
          {
            file_name: match[0].strip,
            line: match[1].to_i,
            severity: match[2].strip,
            code: match[3].strip,
            name: match[4].strip,
            # e.g. function name, nil if outside of a function. Not always available
            scope: match[5].strip.presence,
            result: match[6].strip
          }
        end || []
      end
    rescue Timeout::Error
      assertion_error_matches = []
    end
    concatenated_errors = assertion_error_matches.map { |result| "#{result[:name]}: #{result[:result]}" }.flatten
    {count: count, failed: failed, error_messages: concatenated_errors, detailed_linter_results: assertion_error_matches}
  end

  def self.translate_linter(assessment)
    # The message will be translated once the results were stored in the database
    # See SubmissionScoring for actual function call

    return assessment unless assessment[:detailed_linter_results].present?

    assessment[:detailed_linter_results].map! do |message|
      severity = message[:severity]
      name = message[:name]

      message[:severity] = get_t("linter.#{severity}.severity_name", message[:severity])
      message[:name] = get_t("linter.#{severity}.#{name}.name", message[:name])

      regex = get_t("linter.#{severity}.#{name}.regex", nil)&.strip

      if regex.present?
        captures = message[:result].match(Regexp.new(regex)).named_captures.symbolize_keys

        replacement = captures.each do |key, value|
          value&.replace get_t("linter.#{severity}.#{name}.#{key}.#{value}", value)
        end
      else
        replacement = {}
      end

      replacement.merge!(locale: :de, default: message[:result])
      message[:result] = I18n.t("linter.#{severity}.#{name}.replacement", replacement)
      message
    end

    assessment[:error_messages] = assessment[:detailed_linter_results].map do |message|
      "#{message[:name]}: #{message[:result]}"
    end

    assessment
  rescue StandardError => e
    # A key was not defined or something really bad happened
    Raven.extra_context(assessment)
    Raven.capture_exception(e)
    assessment
  end

  def self.get_t(key, default)
    translation = I18n.t(key, locale: :de, default: default)
    Raven.capture_message({key: key, default: default}) if translation == default
    translation
  end
end
{'fraction_non_alphanumeric': 0.14037103923822034, 'fraction_numerical': 0.006238712855032014, 'mean_word_length': 3.0184696569920844, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1167027', 'n_tokens_mistral': 1934, 'n_tokens_neox': 1881, 'n_words': 410}