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
19 changes: 16 additions & 3 deletions MethodenAnalyser3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2456,11 +2456,23 @@ def _emit_cli_report(report: str) -> None:
sys.stdout.write("\n")


def _write_cli_json_if_requested(report: dict[str, Any], output_path: str | None) -> bool:
def _write_cli_json_if_requested(
report: dict[str, Any], output_path: str | None,
protected_paths: list[str] | None = None,
) -> bool:
"""Schreibt optional den JSON-Report; Fehler gehen nach stderr."""
if not output_path:
return True
try:
target = os.path.normcase(os.path.realpath(output_path))
for source in protected_paths or []:
same_path = target == os.path.normcase(os.path.realpath(source))
same_file = (
os.path.exists(output_path) and os.path.exists(source)
and os.path.samefile(output_path, source)
)
if same_path or same_file:
raise ValueError("JSON-Ausgabe darf keine analysierte Quelldatei ersetzen.")
written_path = write_json_report(report, output_path)
except Exception as exc: # noqa: BLE001 -- CLI boundary reports export failures and returns a failure result
print(f"[FEHLER] {_t('cli_json_export_failed')}: {exc}", file=sys.stderr)
Expand All @@ -2479,7 +2491,7 @@ def _run_cli_file(path: str, json_output: str | None = None) -> int:

_emit_cli_report(generate_report(result))
json_report = build_json_report("file", result, source_name=path)
if not _write_cli_json_if_requested(json_report, json_output):
if not _write_cli_json_if_requested(json_report, json_output, [path]):
return EXIT_ANALYSIS_ERROR
return EXIT_FINDINGS if _file_has_findings(result) else EXIT_OK

Expand All @@ -2498,7 +2510,8 @@ def _run_cli_project(path: str, json_output: str | None = None) -> int:

_emit_cli_report(generate_project_report(result, project_name=_extract_project_display_name(path)))
json_report = build_json_report("project", result, source_name=path)
if not _write_cli_json_if_requested(json_report, json_output):
protected_paths = list(result.file_results) + [p for p, _ in result.files_with_errors]
if not _write_cli_json_if_requested(json_report, json_output, protected_paths):
return EXIT_ANALYSIS_ERROR

if result.files_with_errors:
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-210%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-215%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 @@ -230,7 +230,7 @@ type path\to\file.py | python MethodenAnalyser3.py --stdin --json-output snippet
python MethodenAnalyser3.py --lang en --file path/to/file.py
```

The `--json-output` flag exports a machine-readable report named `methodenanalyser-report-v1.json` (or a custom name if specified). Its structure is documented in [EXPORTFORMAT.md](EXPORTFORMAT.md).
The `--json-output` flag exports a machine-readable report named `methodenanalyser-report-v1.json` (or a custom name if specified). Its structure is documented in [EXPORTFORMAT.md](EXPORTFORMAT.md). Export targets that refer to an analyzed source file, including symbolic links and hard links, are rejected with exit code 1 before writing. This also protects project files that failed analysis.

The local `POST /api/analyze` helper accepts `source_kind` `snippet`, `file`,
and `zip`. A `project` POST is intentionally rejected; project reports are
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-210%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-215%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 @@ -230,7 +230,7 @@ type pfad\zur\datei.py | python MethodenAnalyser3.py --stdin --json-output snipp
python MethodenAnalyser3.py --lang de --file pfad/zur/datei.py
```

Das Flag `--json-output` exportiert einen maschinenlesbaren Bericht namens `methodenanalyser-report-v1.json` (oder unter dem übergebenen Dateinamen). Das Schema ist in [EXPORTFORMAT.md](EXPORTFORMAT.md) beschrieben.
Das Flag `--json-output` exportiert einen maschinenlesbaren Bericht namens `methodenanalyser-report-v1.json` (oder unter dem übergebenen Dateinamen). Das Schema ist in [EXPORTFORMAT.md](EXPORTFORMAT.md) beschrieben. Ausgabeziele, die auf eine analysierte Quelldatei zeigen, werden vor dem Schreiben mit Exit-Code 1 abgelehnt; das gilt auch für symbolische Links und Hard Links sowie für Projektdateien mit Analysefehlern.

Der lokale `POST /api/analyze`-Endpunkt akzeptiert `source_kind` `snippet`, `file`
und `zip`. Ein `project`-POST wird bewusst abgewiesen; Projektberichte entstehen
Expand Down
1 change: 1 addition & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ MethodenAnalyser analyzes Python files and project folders locally with Python A
- CLI file analysis: `python MethodenAnalyser3.py --file path/to/file.py`.
- CLI project analysis: `python MethodenAnalyser3.py --project path/to/project`.
- JSON export: add `--json-output` or pass a target JSON filename.
- CLI JSON export rejects targets that alias analyzed sources (including hard links, symbolic links, and project files with analysis errors); exit code 1, no source write. This check does not lock against concurrent path changes.
- Local web helper: `python webapp/server.py` or `START_WEBAPP.bat`; its API accepts `snippet`, `file` and `zip`, while `project` is import-only.
- Multi-language support: `--lang` parameter supporting `de`, `en`, `es`, `zh`, `ja`, and `ru`.

Expand Down
35 changes: 35 additions & 0 deletions tests/test_json_source_guard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import os

import pytest

import MethodenAnalyser3 as app


@pytest.mark.parametrize("mode", ["file", "project"])
@pytest.mark.parametrize("alias", ["direct", "hardlink", "symlink"])
def test_cli_json_cannot_replace_analyzed_source(tmp_path, mode, alias):
source = tmp_path / "source.py"
original = b"import os\n"
source.write_bytes(original)
output = source
if alias != "direct":
output = tmp_path / "alias.json"
try:
if alias == "hardlink":
os.link(source, output)
else:
output.symlink_to(source)
except OSError as exc:
pytest.skip(f"{alias} unavailable: {exc}")
runner = app._run_cli_file if mode == "file" else app._run_cli_project
assert runner(str(source if mode == "file" else tmp_path), str(output)) == app.EXIT_ANALYSIS_ERROR
assert source.read_bytes() == original
assert output.read_bytes() == original


def test_project_json_cannot_replace_source_with_syntax_error(tmp_path):
source = tmp_path / "broken.py"
original = b"def invalid(:\n"
source.write_bytes(original)
assert app._run_cli_project(str(tmp_path), str(source)) == app.EXIT_ANALYSIS_ERROR
assert source.read_bytes() == original
Loading