From 5fdfb12e62c502e3d73c4b596d79019b556d28a5 Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Sun, 8 Nov 2020 01:04:39 +0100 Subject: [PATCH] Add German translations for PyLint adapter with basic spec --- .../concerns/submission_scoring.rb | 5 +- config/locales/de.linter.yml | 217 ++++++++++++++++++ lib/assessor.rb | 4 + lib/py_lint_adapter.rb | 40 ++++ lib/py_unit_and_py_lint_adapter.rb | 4 + spec/helpers/yaml_spec.rb | 14 ++ 6 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 config/locales/de.linter.yml create mode 100644 spec/helpers/yaml_spec.rb diff --git a/app/controllers/concerns/submission_scoring.rb b/app/controllers/concerns/submission_scoring.rb index 9bf9cda3..93794e65 100644 --- a/app/controllers/concerns/submission_scoring.rb +++ b/app/controllers/concerns/submission_scoring.rb @@ -29,7 +29,10 @@ module SubmissionScoring waiting_for_container_time: output[:waiting_for_container_time] ) - LinterCheckRun.create_from(testrun, assessment) if file.teacher_defined_linter? + if file.teacher_defined_linter? + LinterCheckRun.create_from(testrun, assessment) + assessment = assessor.translate_linter(assessment) + end output.merge!(assessment) output.merge!(filename: file.name_with_extension, message: feedback_message(file, output), weight: file.weight) diff --git a/config/locales/de.linter.yml b/config/locales/de.linter.yml new file mode 100644 index 00000000..2cef2ef2 --- /dev/null +++ b/config/locales/de.linter.yml @@ -0,0 +1,217 @@ +de: + linter: + # This file is used to translate PyLint results from the original English output to German + # The following hierarchy has been implemented: + # + # 1. severity of the linter check result + # a. The `severity_name` translates the severity itself + # 2. code of the linter check result + # 3. A list of required values for the actual translation + # a. example: not used anywhere, just for reference when editing this yml file + # b. name: Title of the linter check + # c. regex: A regex used to translate dynamic parts with _named_ capture groups + # d. replacement: A fix replacement translation which is used instead of the + # original English output. It may refer to one of the named capture + # groups to include dynamic content from the English original + # 4. Optionally a named capture group from the regex + # 5. A list of fix translations for _values / matches_ of the named capture group + # + convention: + severity_name: Konvention + wrong-import-position: + example: Import "from turtle import *" should be placed at the top of the module + name: Falsche Import-Position + regex: .*"(?.*)".* + replacement: Der Import von "%{import}" sollte am Anfang der Datei stehen + bad-whitespace: + example: No space allowed before bracket + name: Inkorrektes Leerzeichen + regex: (?Exactly one space required|No space allowed) (?after|before|around) (?.*) + replacement: "%{where} %{when} %{what}" + what: + No space allowed: sollte kein Leerzeichen stehen + Exactly one space required: sollte genau ein Leerzeichen stehen + where: + before: Vor + after: Hinter + around: Vor und hinter + when: + ':': einem Doppelpunkt + assignment: einer Zuweisung + comma: einem Komma + comparison: einem Vergleich + bracket: einer Klammer + keyword argument assignment: einer Zuweisung von Schlüsselargumenten + multiple-statements: + example: More than one statement on a single line + name: Mehrere Anweisungen + regex: .* + replacement: Mehr als eine Anweisung in einer Zeile + superfluous-parens: + example: Unnecessary parens after 'if' keyword + name: Überflüssige Klammer + regex: .*'(?.*)'.* + replacement: Nach dem Schlüsselwort '%{keyword}' ist keine Klammer notwendig + error: + severity_name: Fehler + function-redefined: + example: function already defined line 15 + name: Funktionsdefinition überschrieben + regex: .*line (?\d*).* + replacement: Eine Funktion mit demselben Namen wurde bereits in Zeile %{line} definiert + import-error: + example: Unable to import 'turtel' + name: Import-Fehler + regex: .*'(?.*)'.* + replacement: Der Import von '%{import} ist fehlgeschlagen + syntax-error: + example: EOL while scanning string literal (, line 1) + name: Syntax-Fehler + regex: | + (?invalid syntax|EOL while scanning string literal|EOF while scanning triple-quoted string literal|cannot assign to|expected an indented block|Missing parentheses in call to|closing parenthesis|expression cannot contain assignment, perhaps you meant|f-string expression part cannot include a backslash|f-string:|invalid character in identifier|invalid decimal literal|trailing comma not allowed without surrounding parentheses|unexpected EOF while parsing|unexpected character after line continuation character|unexpected indent|unindent does not match any outer indentation level|unmatched) ?(?function call|literal|operator|set display|empty expression not allowed|single|unmatched)? ?(?:'(?[^'"]*)'\.*)? ?(?:(?Did you mean|does not match opening parenthesis|is not allowed)(?: ')?)?(?:(?.*)(?:\?|'|"))? ?\((?.*), line (?\d*)\).* + replacement: "%{what}%{what_exactly}%{actual}%{explanation}%{suggestion}" # unused: context, line + what: + invalid syntax: Ungültige Syntax + EOL while scanning string literal: Ein String wurde nicht geschlossen + EOF while scanning triple-quoted string literal: Ein Kommentar mit drei Anführungszeichen wurde nicht geschlossen + cannot assign to: Die Zuweisung ist ungültig für + expected an indented block: Ein eingerückter Codeblock wurde erwartet + Missing parentheses in call to: Die Klammern beim Aufruf von " + closing parenthesis: 'Die schließende Klammer ' + expression cannot contain assignment, perhaps you meant: 'Die Anweisung kann keine Zuweisung enthalten, vielleicht meintest du folgendes: ' + f-string expression part cannot include a backslash: Ein Platzhalter in einem f-String kann keinen Backslash \ enthalten + 'f-string:': 'f-String:' + invalid character in identifier: Ungültiges Zeichen in Bezeichner + invalid decimal literal: Ungültige Zahl # e.g. 100_years + trailing comma not allowed without surrounding parentheses: Ein Komma am Ende einer Aufzählung ist ohne umgebende Klammern nicht erlaubt + unexpected EOF while parsing: Es wurden weitere Zeichen in dem Quellcode erwartet, diese fehlten jedoch + unexpected character after line continuation character: Nach einem Backslash \ außerhalb eines Strings darf in der selben Zeile kein weiteres Zeichen folgen + unexpected indent: Ungültige Einrückung + unindent does not match any outer indentation level: Die Einrückung passt nicht zu einem vorherigen Teil + unmatched: 'Die folgende Klammer scheint zu viel zu sein: ' + what_exactly: + # must start with a space character + function call: ' eine Funktion' + literal: ' eine Zahl' + operator: ' einen Operator' + set display: ' ein Set' + list display: ' eine Liste' + dict display: ' ein Dictionary' + f-string expression: ' einem F-String' + # the following are in the context of an f-string + empty expression not allowed: ' eine leere Anweisung ist nicht erlaubt' + single: ' eine einzelne "' + unmatched: ' unpassende ' + explanation: + Did you mean: '" fehlen. Vielleicht meintest du folgendes:' + does not match opening parenthesis: ' passt nicht zu der öffnenden Klammer ' + is not allowed: '" ist nicht erlaubt' + # additional capture groups that are used without translation: + # - actual + # - suggestion + # - context + # - line + undefined-variable: + example: Undefined variable 'beginn_fill' + name: Undefinierter Bezeichner + regex: .*'(?.*)'.* + replacement: Der Name '%{name}' ist unbekannt + used-before-assignment: + example: Using variable 'kleidung' before assignment + name: Verwendung vor Zuweisung + regex: .*'(?.*)'.* + replacement: Die Variable '%{name}' wird vor ihrer erstmaligen Zuweisung verwendet + return-outside-function: + example: Return outside function + name: Return außerhalb einer Funktion + regex: .* + replacement: Ein Return kann nur innerhalb einer Funktion verwendet werden + refactor: + severity_name: Überarbeitung empfohlen + comparison-with-itself: + example: Redundant comparison - hauptspeise == hauptspeise + name: Vergleich mit sich selbst + regex: .* - (?.*) + replacement: Der Vergleich ist überflüssig - %{comparison} + inconsistent-return-statements: + example: Either all return statements in a function should return an expression, or none of them should. + name: Uneinheitliche Rückgabewerte + regex: .* + replacement: Entweder sollten alle return Anweisungen in einer Funktion ein Ergebnis zurückgeben oder keine Anweisung sollte einen Rückgabewert haben + redefined-argument-from-local: + example: Redefining argument with the local name 'Wort' + name: Überschreibung eines Arguments + regex: .*'(?.*)'.* + replacement: Das Argument '%{name}' wird überschrieben + warning: + severity_name: Warnung + bad-indentation: + example: Bad indentation. Found 3 spaces, expected 4 + name: Ungütlige Einrückung + regex: .*(?\d*).*(?\d*).* + replacement: Ungültige Einrückung. Statt '%{actual}' Leerzeichen wurden %{expected} Leerzeichen erwartet + duplicate-key: + example: Duplicate key 100 in dictionary + name: Doppelter Schlüssel + regex: Duplicate key (?.*) in dictionary + replacement: Der Schlüssel '%{key}' ist im Dictionary doppelt vorhanden + duplicate-except: + example: Catching previously caught exception type ValueError + name: Doppeltes Except + regex: Catching previously caught exception type (?.*) + replacement: Die zuvor bereits aufgefangene Exception '%{exception}' wird erneut behandelt + mixed-indentation: + example: Found indentation with tabs instead of spaces + name: Gemischte Einrückung + regex: .* + replacement: Es wurde eine Einrückung mit Tabs anstelle von Leerzeichen entdeckt + pointless-statement: + example: Statement seems to have no effect + name: sinnlose Anweisung + regex: .* + replacement: Die Anweisung scheint keine Auswirkungen zu haben + pointless-string-statement: + example: String statement has no effect + name: sinnloser String + regex: .* + replacement: Ein einzelner String ohne Zuweisung hat keine Auswirkung + redefined-builtin: + example: Redefining built-in 'print' + name: Überschreibung + regex: .*'(?.*)'.* + replacement: Der interne Bezeichner '%{builtin}' wird überschrieben + redefined-outer-name: + example: Redefining name 'name' from outer scope (line 1) + name: Überschreibung eines äußeren Bezeichners + regex: .*'(?.*)'.*\(line (?\d*)\).* + replacement: Der Bezeichner '%{name}', der bereits in Zeile %{line} definiert wurde, wird überschrieben + self-assigning-variable: + example: Assigning the same variable 'kleidung' to itself + name: Selbstzuweisung + regex: .*'(?.*)'.* + replacement: Die Variable '%{name}' wird sich selbst zugewiesen + unreachable: + example: Unreachable code + name: Unerreichbar + regex: .* + replacement: Die Anweisung wird nie ausgeführt werden + undefined-loop-variable: + example: Using possibly undefined loop variable 'i' + name: Unbekannte Schleifenvariable + regex: .*'(?.*)'.* + replacement: Die Schleifenvariable '%{name}' ist möglicherweise nicht definiert + unnecessary-semicolon: + example: Unnecessary semicolon + name: Unnötiges Semikolon + regex: .* + replacement: Das Semikolon ist unnötig + unused-argument: + example: Unused argument 'laenge' + name: Unbenutztes Argument + regex: .*'(?.*)'.* + replacement: Das Argument '%{name}' wird nicht verwendet + unused-variable: + example: Unused variable 'i' + name: Unbenutzte Variable + regex: .*'(?.*)'.* + replacement: Die Variable '%{name}' wird nicht verwendet diff --git a/lib/assessor.rb b/lib/assessor.rb index 910b5939..5febbb26 100644 --- a/lib/assessor.rb +++ b/lib/assessor.rb @@ -27,5 +27,9 @@ class Assessor end end + def translate_linter(result) + @testing_framework_adapter.translate_linter(result) + end + class Error < RuntimeError; end end diff --git a/lib/py_lint_adapter.rb b/lib/py_lint_adapter.rb index 2447b37a..e3e68681 100644 --- a/lib/py_lint_adapter.rb +++ b/lib/py_lint_adapter.rb @@ -39,4 +39,44 @@ class PyLintAdapter < TestingFrameworkAdapter 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 + + 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 diff --git a/lib/py_unit_and_py_lint_adapter.rb b/lib/py_unit_and_py_lint_adapter.rb index 4b6dea5f..52dbe74e 100644 --- a/lib/py_unit_and_py_lint_adapter.rb +++ b/lib/py_unit_and_py_lint_adapter.rb @@ -11,4 +11,8 @@ class PyUnitAndPyLintAdapter < TestingFrameworkAdapter PyUnitAdapter.new.parse_output(output) end end + + def translate_linter(result) + PyLintAdapter.translate_linter(result) + end end diff --git a/spec/helpers/yaml_spec.rb b/spec/helpers/yaml_spec.rb new file mode 100644 index 00000000..6518edfd --- /dev/null +++ b/spec/helpers/yaml_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'find' +require 'yaml' + +describe 'yaml config files' do + Find.find(__dir__, 'config') do |path| + next unless path =~ /.*.\.yml/ + + it "loads #{path} without syntax error" do + expect { YAML.load_file(path) }.not_to raise_error + end + end +end