87 lines
2.7 KiB
Python
87 lines
2.7 KiB
Python
"""Statische Prüfung beim Commit, ohne Code auszuführen (→ Phase 3).
|
|
|
|
Python (ast), JSON, TOML, XML (Standardbibliothek) und YAML (PyYAML). Fehler sind
|
|
Warnungen: Der Commit entsteht trotzdem, die KI bekommt sie zurück, und sie stehen
|
|
am Commit in der Ansicht „Änderungen“.
|
|
"""
|
|
|
|
import ast
|
|
import json
|
|
import tomllib
|
|
import warnings as py_warnings
|
|
import xml.etree.ElementTree as ElementTree
|
|
from pathlib import PurePosixPath
|
|
|
|
import yaml
|
|
|
|
MAX_WARNINGS = 20
|
|
|
|
|
|
def python(text: str) -> str | None:
|
|
try:
|
|
with py_warnings.catch_warnings():
|
|
py_warnings.simplefilter("ignore") # z. B. SyntaxWarning zu Escape-Sequenzen
|
|
ast.parse(text)
|
|
except SyntaxError as e:
|
|
return f"{e.lineno or '?'}: Python-Syntaxfehler: {e.msg}"
|
|
return None
|
|
|
|
|
|
def json_(text: str) -> str | None:
|
|
try:
|
|
json.loads(text)
|
|
except ValueError as e:
|
|
return f"{getattr(e, 'lineno', '?')}: ungültiges JSON: {getattr(e, 'msg', str(e))}"
|
|
return None
|
|
|
|
|
|
def toml(text: str) -> str | None:
|
|
try:
|
|
tomllib.loads(text)
|
|
except tomllib.TOMLDecodeError as e:
|
|
return f"?: ungültiges TOML: {e}"
|
|
return None
|
|
|
|
|
|
def xml(text: str) -> str | None:
|
|
# ElementTree löst keine externen Entitäten auf; Expat ab 2.4 begrenzt Entitäts-Expansion
|
|
try:
|
|
ElementTree.fromstring(text)
|
|
except ElementTree.ParseError as e:
|
|
return f"{e.position[0]}: ungültiges XML: {e}"
|
|
return None
|
|
|
|
|
|
def yaml_(text: str) -> str | None:
|
|
# Nur die Struktur prüfen (compose): keine Objekte bauen, unbekannte Tags wie !Ref (CloudFormation) erlaubt;
|
|
# mehrere Dokumente (---) sind zulässig
|
|
loader = getattr(yaml, "CSafeLoader", yaml.SafeLoader)
|
|
try:
|
|
for _ in yaml.compose_all(text, Loader=loader):
|
|
pass
|
|
except yaml.YAMLError as e:
|
|
mark = getattr(e, "problem_mark", None)
|
|
problem = getattr(e, "problem", None) or str(e).splitlines()[0]
|
|
return f"{mark.line + 1 if mark else '?'}: ungültiges YAML: {problem}"
|
|
return None
|
|
|
|
|
|
CHECKS = {".py": python, ".pyi": python, ".json": json_, ".ipynb": json_, ".toml": toml, ".xml": xml,
|
|
".svg": xml, ".xsd": xml, ".pom": xml, ".yaml": yaml_, ".yml": yaml_}
|
|
|
|
|
|
def check(files: dict[str, bytes]) -> list[str]:
|
|
found = []
|
|
for path, content in files.items():
|
|
checker = CHECKS.get(PurePosixPath(path).suffix.lower())
|
|
if checker is None:
|
|
continue
|
|
try:
|
|
text = content.decode("utf-8")
|
|
except UnicodeDecodeError:
|
|
found.append(f"{path}: kein gültiges UTF-8")
|
|
continue
|
|
problem = checker(text)
|
|
if problem:
|
|
found.append(f"{path}:{problem}")
|
|
return found[:MAX_WARNINGS]
|