tools/check_nfalle.py: Scan gegen die \n-Falle — Projekt ist sauber
Auftrag "ueberpruefe alle dateien auf \n-Falle". Die Falle: ueber ein
Shell-Heredoc geschriebener Code macht aus einem gemeinten \n einen ECHTEN
Zeilenumbruch mitten im String-Literal. Heute zweimal passiert (Telegram-Text
in engine.py, Reminder-Eintrag in measurement_reminder.py).
Der Scan prueft in beide Richtungen:
A) kompiliert alle .py (161 Dateien) — sauber
B) JS: unabgeschlossene String-Literale — app.js sauber
C) UMGEKEHRT: literales \n als sichtbarer Text in HTML-title= und Markdown
— alle sauber
D) Signatur der beiden heutigen Faelle (Zeile endet offen, naechste besteht
nur aus einem Quote) — keine
⚠ Fehlalarm im Scanner selbst gefunden und behoben: Quotes zu ZAEHLEN meldet
'"' als offenes Literal (gueltiges JS mit einem Anfuehrungszeichen darin, real
in app.js). Der Scan verfolgt jetzt zeichenweise den AKTIVEN Quote-Typ.
Die betroffene app.js-Stelle trotzdem aufgeraeumt: schliessendes
Anfuehrungszeichen jetzt als U+201C statt ueber eine Konkatenation mit '"'.
Regel in CLAUDE.md ergaenzt: mehrzeilige Strings mit \n nicht per Heredoc
schreiben, sondern ueber den Edit-Weg.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b858ae9aa2
commit
5a89544a27
@@ -0,0 +1,153 @@
|
||||
"""Sucht die \\n-Falle in allen Projektdateien — in BEIDE Richtungen.
|
||||
|
||||
Die Falle: beim Schreiben ueber ein Shell-Heredoc wird aus einem gemeinten `\\n`
|
||||
ein ECHTER Zeilenumbruch mitten im String-Literal. In Python bricht das den
|
||||
Compile (zweimal heute passiert), in JS ebenso — aber nicht ueberall faellt es
|
||||
sofort auf.
|
||||
|
||||
Geprueft wird:
|
||||
A) Python: kompiliert jede Datei. Ein zerrissenes Literal faellt hier auf.
|
||||
B) JS: Zeilen mit ungerader Zahl UNMASKIERTER Anfuehrungszeichen ausserhalb
|
||||
von Kommentaren = angefangenes, nicht geschlossenes String-Literal.
|
||||
C) Umgekehrter Fall: literales `\\n` als TEXT dort, wo es niemand lesen will
|
||||
(HTML-Attribute, Markdown-Fliesstext) — das entsteht, wenn ein `\\\\n`
|
||||
zu viel escaped wurde.
|
||||
D) Verdaechtige Zeilenpaare: eine Zeile endet mit oeffnendem Quote, die
|
||||
naechste besteht nur aus einem Quote — die Signatur der beiden heutigen
|
||||
Faelle.
|
||||
"""
|
||||
import pathlib
|
||||
import py_compile
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
WURZEL = pathlib.Path(r"c:\Users\ah\Downloads\tradingrobot")
|
||||
SKIP = {".git", "__pycache__", ".reminders", "node_modules", ".removed_backup"}
|
||||
befunde = []
|
||||
|
||||
|
||||
def dateien(*endungen):
|
||||
for p in WURZEL.rglob("*"):
|
||||
if any(s in p.parts for s in SKIP):
|
||||
continue
|
||||
if p.is_file() and p.suffix in endungen:
|
||||
yield p
|
||||
|
||||
|
||||
# ── A) Python ────────────────────────────────────────────────────────────
|
||||
print("=" * 88)
|
||||
print(" A) PYTHON — kompiliert jede Datei (ein zerrissenes Literal faellt hier auf)")
|
||||
print("=" * 88)
|
||||
npy = 0
|
||||
tmp = tempfile.mkdtemp()
|
||||
for p in dateien(".py"):
|
||||
npy += 1
|
||||
try:
|
||||
py_compile.compile(str(p), cfile=f"{tmp}/x.pyc", doraise=True)
|
||||
except py_compile.PyCompileError as e:
|
||||
msg = str(e).strip().splitlines()[-1][:110]
|
||||
befunde.append(f"PY {p.relative_to(WURZEL)}: {msg}")
|
||||
print(f" FEHLER {p.relative_to(WURZEL)}")
|
||||
print(f" {msg}")
|
||||
print(f" {npy} Dateien geprueft.")
|
||||
|
||||
|
||||
# ── B) JavaScript ────────────────────────────────────────────────────────
|
||||
def js_offene_strings(text: str):
|
||||
"""Zeilen mit angefangenem, nicht geschlossenem ' oder " (Template-Literale
|
||||
duerfen mehrzeilig sein und werden uebersprungen)."""
|
||||
treffer = []
|
||||
in_tmpl = False
|
||||
for nr, z in enumerate(text.splitlines(), 1):
|
||||
# Backticks zaehlen -> Template-Literal-Zustand fortschreiben
|
||||
bt = len(re.findall(r"(?<!\\)`", z))
|
||||
if in_tmpl:
|
||||
in_tmpl ^= bool(bt % 2)
|
||||
continue
|
||||
if bt % 2:
|
||||
in_tmpl = True
|
||||
continue
|
||||
s = re.sub(r"//.*$", "", z) # Zeilenkommentar weg
|
||||
# ⚠ Quotes NICHT einfach zaehlen — das erzeugt Fehlalarme: '"' ist ein
|
||||
# gueltiges Literal, das EIN doppeltes Anfuehrungszeichen enthaelt (real
|
||||
# in app.js aufgetreten). Stattdessen zeichenweise laufen und den jeweils
|
||||
# AKTIVEN Quote-Typ verfolgen; Quotes der anderen Art zaehlen dann nicht.
|
||||
aktiv, esc = None, False
|
||||
for ch in s:
|
||||
if esc:
|
||||
esc = False
|
||||
continue
|
||||
if ch == "\\":
|
||||
esc = True
|
||||
continue
|
||||
if aktiv:
|
||||
if ch == aktiv:
|
||||
aktiv = None
|
||||
elif ch in ('"', "'"):
|
||||
aktiv = ch
|
||||
if aktiv:
|
||||
treffer.append((nr, z.strip()[:100]))
|
||||
return treffer
|
||||
|
||||
|
||||
print()
|
||||
print("=" * 88)
|
||||
print(" B) JAVASCRIPT — unabgeschlossene String-Literale")
|
||||
print("=" * 88)
|
||||
for p in dateien(".js"):
|
||||
if "lightweight-charts" in p.name:
|
||||
continue
|
||||
tr = js_offene_strings(p.read_text(encoding="utf-8", errors="replace"))
|
||||
if tr:
|
||||
for nr, z in tr:
|
||||
befunde.append(f"JS {p.relative_to(WURZEL)}:{nr}")
|
||||
print(f" FEHLER {p.relative_to(WURZEL)}:{nr} {z}")
|
||||
else:
|
||||
print(f" OK {p.relative_to(WURZEL)}")
|
||||
|
||||
|
||||
# ── C) literales \n als TEXT ─────────────────────────────────────────────
|
||||
print()
|
||||
print("=" * 88)
|
||||
print(" C) UMGEKEHRT — literales \\n im sichtbaren Text (HTML-Attribute, Markdown)")
|
||||
print("=" * 88)
|
||||
for p in list(dateien(".html")) + list(dateien(".md")):
|
||||
t = p.read_text(encoding="utf-8", errors="replace")
|
||||
roh = []
|
||||
for m in re.finditer(r'title="([^"]*)"', t):
|
||||
if "\\n" in m.group(1):
|
||||
roh.append(f"title=...{m.group(1)[:60]}")
|
||||
if p.suffix == ".md":
|
||||
for nr, z in enumerate(t.splitlines(), 1):
|
||||
if "\\n" in z and "`" not in z and "\\\\n" not in z:
|
||||
roh.append(f"Zeile {nr}: {z.strip()[:80]}")
|
||||
if roh:
|
||||
for r in roh[:5]:
|
||||
befunde.append(f"TXT {p.relative_to(WURZEL)}: {r}")
|
||||
print(f" FEHLER {p.relative_to(WURZEL)} {r}")
|
||||
else:
|
||||
print(f" OK {p.relative_to(WURZEL)}")
|
||||
|
||||
|
||||
# ── D) Signatur der heutigen Faelle ──────────────────────────────────────
|
||||
print()
|
||||
print("=" * 88)
|
||||
print(' D) SIGNATUR — Zeile endet offen, naechste besteht nur aus einem Quote')
|
||||
print("=" * 88)
|
||||
sig = 0
|
||||
for p in list(dateien(".py")) + list(dateien(".js")):
|
||||
zeilen = p.read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
for i in range(len(zeilen) - 1):
|
||||
if zeilen[i + 1].strip() in ('"', "'", '",', "',"):
|
||||
sig += 1
|
||||
befunde.append(f"SIG {p.relative_to(WURZEL)}:{i+2}")
|
||||
print(f" VERDAECHTIG {p.relative_to(WURZEL)}:{i+2}")
|
||||
print(" keine gefunden." if not sig else f" {sig} Stellen.")
|
||||
|
||||
print()
|
||||
print("=" * 88)
|
||||
print(" ERGEBNIS: " + ("keine \\n-Falle gefunden" if not befunde
|
||||
else f"{len(befunde)} Befund(e)"))
|
||||
print("=" * 88)
|
||||
sys.exit(1 if befunde else 0)
|
||||
Reference in New Issue
Block a user