Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 73 additions & 21 deletions MethodenAnalyser3.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import pkgutil
import re
import sqlite3
import stat
import sys
import tempfile
import threading
import tkinter as tk
import warnings
Expand Down Expand Up @@ -1490,10 +1492,9 @@ def _remove_unused_imports(source: str, tree: ast.AST, unused_set: set[str]) ->
"""Remove AST import spans without deleting neighboring executable code."""
raw = source.encode("utf-8")
offsets = [0]
# AST columns count UTF-8 bytes; only LF advances AST line numbers.
# str.splitlines() would incorrectly count form-feed as a new line.
for line in raw.split(b"\n")[:-1]:
offsets.append(offsets[-1] + len(line) + 1)
# AST columns count UTF-8 bytes. Recognize physical CR/LF boundaries
# without counting form-feed or rewriting untouched newline sequences.
offsets.extend(match.end() for match in re.finditer(rb"\r\n|\r|\n", raw))
nodes = sorted(_unused_import_nodes(tree, unused_set),
key=lambda node: (node.lineno, node.col_offset), reverse=True)
changed_lines: set[int] = set()
Expand All @@ -1519,6 +1520,64 @@ def _remove_unused_imports(source: str, tree: ast.AST, unused_set: set[str]) ->
return updated, changed_lines


def _stage_auto_fix_bytes(path: pathlib.Path, data: bytes) -> pathlib.Path:
"""Fully write and flush a same-directory temporary file."""
staged = None
complete = False
try:
with tempfile.NamedTemporaryFile(mode="wb", dir=path.parent,
prefix=f".{path.name}.autofix-", delete=False) as handle:
staged = pathlib.Path(handle.name)
if handle.write(data) != len(data):
raise OSError("Incomplete Auto-Fix write")
handle.flush()
os.fsync(handle.fileno())
complete = True
return staged
finally:
if staged is not None and not complete:
staged.unlink(missing_ok=True)


def _commit_auto_fix(path: str, original: bytes, updated: bytes) -> str:
"""Publish a complete backup exclusively, then atomically replace the source."""
source = pathlib.Path(path).resolve(strict=True)
mode = stat.S_IMODE(source.stat().st_mode)
staged_source = staged_backup = None
try:
staged_source = _stage_auto_fix_bytes(source, updated)
staged_backup = _stage_auto_fix_bytes(source, original)
if source.read_bytes() != original:
raise OSError("Source changed during Auto-Fix")
index = 0
while True:
suffix = f".bak.{index}" if index else ".bak"
backup = pathlib.Path(str(source) + suffix)
try:
# Same-directory hard-link publication is atomic and exclusive:
# an existing or concurrently created recovery copy is preserved.
os.link(staged_backup, backup)
except FileExistsError:
index += 1
else:
break
os.chmod(staged_source, mode)
if source.read_bytes() != original:
raise OSError("Source changed during Auto-Fix")
os.replace(staged_source, source)
return str(backup)
finally:
for staged in (staged_source, staged_backup):
if staged is not None:
try:
# A staged source may carry the original read-only flag.
# Clear it only on our temporary path before cleanup.
staged.chmod(stat.S_IRUSR | stat.S_IWUSR)
except FileNotFoundError:
continue
staged.unlink(missing_ok=True)


def auto_fix_unused_imports(output_widget: scrolledtext.ScrolledText, status_widget: tk.Label | None = None) -> None:
"""
Entfernt ungenutzte Imports aus der zuletzt analysierten Datei.
Expand Down Expand Up @@ -1551,24 +1610,22 @@ def auto_fix_unused_imports(output_widget: scrolledtext.ScrolledText, status_wid
try:
# Datei lesen mit Encoding-Fallback — erkanntes Encoding für Schreibzugriff merken
detected_encoding = "utf-8"
original_bytes = pathlib.Path(_last_analysis_path).read_bytes()
try:
with open(_last_analysis_path, "r", encoding="utf-8") as f:
lines = f.readlines()
original_text = original_bytes.decode("utf-8")
except UnicodeDecodeError:
detected_encoding = "latin-1"
with open(_last_analysis_path, "r", encoding="latin-1") as f:
lines = f.readlines()

# AST parsen (readlines() beibehalten — splitlines() würde bei \x0c
# Zeilennummern gegenüber AST-lineno verschieben und falsche Zeilen löschen)
tree = ast.parse("".join(lines))
original_text = original_bytes.decode("latin-1")
# Parse the original text; physical byte offsets preserve mixed
# newline sequences and avoid treating form-feed as a new line.
tree = ast.parse(original_text)

# Import-Zeilen markieren die entfernt werden sollen
# Findings may be stale after an editor change or the confirmation
# dialog. Remove only names approved earlier AND still unused now.
current_result = analyze_source("".join(lines), _last_analysis_path)
current_result = analyze_source(original_text, _last_analysis_path)
unused_set = set(_last_analysis_result.unused_imports) & set(current_result.unused_imports)
updated_source, lines_to_remove = _remove_unused_imports("".join(lines), tree, unused_set)
updated_source, lines_to_remove = _remove_unused_imports(original_text, tree, unused_set)

if not lines_to_remove:
if status_widget is not None:
Expand All @@ -1581,13 +1638,8 @@ def auto_fix_unused_imports(output_widget: scrolledtext.ScrolledText, status_wid

# Backup und Ausgabe im erkannten Encoding — verhindert Korrumpierung von
# latin-1-Dateien mit nicht-ASCII-Zeichen und # coding: latin-1 Deklaration
backup_path = _last_analysis_path + ".bak"
with open(backup_path, "w", encoding=detected_encoding) as f:
f.writelines(lines)

# Neue Datei ohne ungenutzte Imports
with open(_last_analysis_path, "w", encoding=detected_encoding) as f:
f.write(updated_source)
updated_bytes = updated_source.encode(detected_encoding)
backup_path = _commit_auto_fix(_last_analysis_path, original_bytes, updated_bytes)

# Ausgabe
output_widget.insert(tk.END, f"\n\n{_t('gui_autofix_success_header')}\n")
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<p align="center">
<a href="https://github.com/dev-bricks/MethodenAnalyser/releases/tag/v3.0.2"><img src="https://img.shields.io/badge/Version-3.0.2-blue?style=for-the-badge" alt="Version 3.0.2"></a>
<a href="https://github.com/dev-bricks/MethodenAnalyser/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/CI-Passing-success?style=for-the-badge&logo=githubactions&logoColor=white" alt="CI Status"></a>
<a href="#development--testing"><img src="https://img.shields.io/badge/Tests-189%20Passed-success?style=for-the-badge&logo=pytest&logoColor=white" alt="Tests 100% Green"></a>
<a href="#development--testing"><img src="https://img.shields.io/badge/Tests-203%20Passed-success?style=for-the-badge&logo=pytest&logoColor=white" alt="Tests 100% Green"></a>
<img src="https://img.shields.io/badge/Python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-yellow?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.10-3.13">
<img src="https://img.shields.io/badge/Platform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey?style=for-the-badge" alt="Cross-Platform">
<img src="https://img.shields.io/badge/GUI-Tkinter%20%2B%20CLI%20%2B%20PWA-orange?style=for-the-badge" alt="GUI Desktop + CLI + PWA">
Expand Down Expand Up @@ -173,7 +173,7 @@ MethodenAnalyser enforces 10 strict architectural, runtime, and security invaria
| **INV-ENCODING-04** | Lossless Multi-Encoding Resilience | Default UTF-8 reading with automatic Latin-1 fallback. Never corrupts original character encodings, UTF-8 BOMs, or German umlauts. |
| **INV-ISOLATION-05** | Loopback Web Companion Isolation | Local helper companion binds strictly to `127.0.0.1` by default. Deliberate `--host 0.0.0.0` LAN test mode requires explicit user configuration. |
| **INV-TRAVERSAL-06** | Anti-Path-Traversal & ZIP Safety | Web companion rejects path traversal (`..`), bounds member counts, and enforces maximum archive byte limits to prevent ZIP bombs. |
| **INV-AUTOFIX-07** | Reversible Auto-Fix with Backup | Auto-Fix requires explicit confirmation, rechecks current source usage and creates a `.bak` backup before writing modifications. |
| **INV-AUTOFIX-07** | Reversible Auto-Fix with Backup | Auto-Fix requires explicit confirmation, rechecks current source usage and publishes a byte-exact backup without replacing existing copies (`.bak`, `.bak.1`, …), then atomically replaces the source from a flushed temporary file. Detected source changes abort the write; encoding, newline style and file mode are retained. |
| **INV-PLATFORM-08** | Multi-Platform Parity | 100% standard library core verified across Windows Server 2025, Ubuntu Linux, and macOS 26. |
| **INV-SYNC-09** | Cloud-Sync & Multi-Agent Lock Safety | Fails closed on lock detection (`LOCK.*`), ignores cloud sync conflict files (`*-conflict-*`), preventing concurrency races. |
| **INV-SLA-10** | 48h Security SLA & 5-Day Triage | Strict vulnerability commitment: 48-hour initial response SLA and 5-business-day triage commitment with coordinated disclosure. |
Expand Down
4 changes: 2 additions & 2 deletions README_de.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<p align="center">
<a href="https://github.com/dev-bricks/MethodenAnalyser/releases/tag/v3.0.2"><img src="https://img.shields.io/badge/Version-3.0.2-blue?style=for-the-badge" alt="Version 3.0.2"></a>
<a href="https://github.com/dev-bricks/MethodenAnalyser/actions/workflows/tests.yml"><img src="https://img.shields.io/badge/CI-Bestanden-success?style=for-the-badge&logo=githubactions&logoColor=white" alt="CI-Status"></a>
<a href="#entwicklung--tests"><img src="https://img.shields.io/badge/Tests-189%20Bestanden-success?style=for-the-badge&logo=pytest&logoColor=white" alt="Tests 100% Grün"></a>
<a href="#entwicklung--tests"><img src="https://img.shields.io/badge/Tests-203%20Bestanden-success?style=for-the-badge&logo=pytest&logoColor=white" alt="Tests 100% Grün"></a>
<img src="https://img.shields.io/badge/Python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-yellow?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.10-3.13">
<img src="https://img.shields.io/badge/Plattform-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey?style=for-the-badge" alt="Plattformübergreifend">
<img src="https://img.shields.io/badge/GUI-Tkinter%20%2B%20CLI%20%2B%20PWA-orange?style=for-the-badge" alt="GUI Desktop + CLI + PWA">
Expand Down Expand Up @@ -173,7 +173,7 @@ MethodenAnalyser garantiert 10 verbindliche Architektur-, Laufzeit- und Sicherhe
| **INV-ENCODING-04** | Verlustfreie Multi-Encoding-Resilienz | Liest Dateien standardmäßig als UTF-8 mit automatischem Latin-1 Fallback. Verändert niemals Original-Encodings oder deutsche Umlaute. |
| **INV-ISOLATION-05** | Loopback Web-Begleiter-Isolation | Der lokale Hilfsserver bindet sich standardmäßig ausschließlich an `127.0.0.1`. Der LAN-Modus `--host 0.0.0.0` erfordert explizite Aktivierung. |
| **INV-TRAVERSAL-06** | Pfad-Traversal- & ZIP-Sicherheit | Der Web-Begleiter blockiert Directory-Traversal (`..`), begrenzt Dateizahlen und unkomprimierte Byte-Mengen zur Abwehr von ZIP-Bomben. |
| **INV-AUTOFIX-07** | Reversibler Auto-Fix mit Backup | Auto-Fix erfordert eine explizite Bestätigung, prüft die aktuelle Verwendung erneut und legt vor jeder Änderung eine `.bak`-Sicherheitskopie an. |
| **INV-AUTOFIX-07** | Reversibler Auto-Fix mit Backup | Auto-Fix erfordert eine explizite Bestätigung, prüft die aktuelle Verwendung erneut und veröffentlicht eine bytegenaue Sicherung ohne bestehende Backups zu überschreiben (`.bak`, `.bak.1`, …) und ersetzt die Quelle anschließend atomar aus einer vollständig geschriebenen temporären Datei. Erkannte Quelländerungen brechen den Schreibvorgang ab; Kodierung, Zeilenumbruchstil und Dateimodus bleiben erhalten. |
| **INV-PLATFORM-08** | Plattformübergreifende Parität | Vollständige Standardbibliothek-Kompatibilität, verifiziert auf Windows Server 2025, Ubuntu Linux und macOS 26. |
| **INV-SYNC-09** | Cloud-Sync- & Lock-Resilienz | Beachtet Dateisperren (`LOCK.*`) im Fail-Closed-Verfahren und ignoriert Cloud-Sync-Konfliktdateien (`*-conflict-*`). |
| **INV-SLA-10** | 48h Sicherheits-SLA & 5-Tage-Triage | Verbindliches Sicherheitsversprechen: Eingangsbestätigung binnen 48 Stunden, Triage und Behebungsplan binnen 5 Werktagen. |
Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ The tool strictly adheres to 10 architectural and security invariants:
- INV-ENCODING-04: Lossless Multi-Encoding Resilience (UTF-8 default with Latin-1 fallback)
- INV-ISOLATION-05: Loopback Web Companion Isolation (binds to 127.0.0.1 by default)
- INV-TRAVERSAL-06: Path Traversal & ZIP Safety Bounds (blocks `..` and bounds member sizes)
- INV-AUTOFIX-07: Reversible Auto-Fix with `.bak` Backup; current source is rechecked after confirmation, and only approved imports that remain unused are removed. Import spans preserve neighboring statements; empty suites retain pass, and the result is syntax-checked before writing.
- INV-AUTOFIX-07: Reversible Auto-Fix with `.bak` Backup; current source is rechecked after confirmation, and only approved imports that remain unused are removed. Import spans preserve neighboring statements; empty suites retain pass, and the result is syntax-checked before writing. Auto-Fix stages and flushes complete byte payloads, exclusively publishes numbered backups via same-directory hard links and atomically replaces the source. Detected source changes abort; original bytes remain in the backup. Unsupported backup publication fails before source replacement.
- INV-PLATFORM-08: Cross-Platform Parity across Windows, Linux, and macOS
- INV-SYNC-09: Cloud-Sync & Multi-Agent Lock Resilience (fail-closed against `LOCK.*`)
- INV-SLA-10: 48h Initial Response & 5-Business-Day Triage SLA
Expand Down
Loading
Loading