Proxy: local abaplint messages on a bare 'save failed' (EPOD syntax check cannot see the rejected source)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
86
harness/localcheck.py
Normal file
86
harness/localcheck.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Local syntax check with abaplint (parser errors) for a source that SAP could not save.
|
||||
|
||||
SAP answers a failed write with "An error occured during the save operation" and nothing else, and the
|
||||
EPOD syntax check reads the stored version (the old one or none), so it cannot check the rejected source.
|
||||
The proxy runs this check on the rejected source and adds the messages (line + text) to the tool result.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ABAPLINT = os.path.join(ROOT, "node_modules", ".bin", "abaplint")
|
||||
VERSIONS = {"v702", "v740sp05", "v750", "v758"}
|
||||
BARE_SAVE_FAILURE = re.compile(r"during the save operation", re.I)
|
||||
|
||||
|
||||
def is_bare_save_failure(text):
|
||||
"""True when the write result is the detail-free 'save operation' error."""
|
||||
try:
|
||||
obj = json.loads(text)
|
||||
except (TypeError, ValueError):
|
||||
return bool(BARE_SAVE_FAILURE.search(str(text)))
|
||||
if not isinstance(obj, dict) or obj.get("success") is not False:
|
||||
return False
|
||||
return bool(BARE_SAVE_FAILURE.search(str(obj.get("error") or obj.get("message") or ""))) \
|
||||
and not (obj.get("activation") or {}).get("messages")
|
||||
|
||||
|
||||
def filename(object_type, name, include_type=None, function_group=None):
|
||||
n = re.sub(r"\W", "_", str(name).lower())
|
||||
t = str(object_type).upper()
|
||||
if t == "CLAS":
|
||||
return f"{n}.clas.{'testclasses.' if str(include_type).lower() == 'testclasses' else ''}abap"
|
||||
if t == "INTF":
|
||||
return f"{n}.intf.abap"
|
||||
if t == "PROG":
|
||||
return f"{n}.prog.abap"
|
||||
if t == "FUNC" and function_group:
|
||||
fg = re.sub(r"\W", "_", str(function_group).lower())
|
||||
return f"{fg}.fugr.{n}.abap"
|
||||
if t == "DDLS":
|
||||
return f"{n}.ddls.asddls"
|
||||
return None # TABL, FUGR, ...: no local parser check
|
||||
|
||||
|
||||
def parser_messages(object_type, name, source, include_type=None, function_group=None, release=None):
|
||||
"""[{"line", "severity", "message"}] from abaplint parser_error; [] when nothing is found or no check."""
|
||||
fn = filename(object_type, name, include_type, function_group)
|
||||
if not fn or not source or not os.path.exists(ABAPLINT):
|
||||
return []
|
||||
version = release if release in VERSIONS else "v758"
|
||||
d = tempfile.mkdtemp(prefix="proxylint_")
|
||||
try:
|
||||
os.makedirs(os.path.join(d, "src"))
|
||||
open(os.path.join(d, "src", fn), "w").write(source)
|
||||
cfg = {"global": {"files": "/src/**/*.*"}, "dependencies": [],
|
||||
"syntax": {"version": version, "errorNamespace": "^(Z|Y)"}, "rules": {"parser_error": True}}
|
||||
json.dump(cfg, open(os.path.join(d, "abaplint.json"), "w"))
|
||||
p = subprocess.run([ABAPLINT, "abaplint.json", "-f", "json"], cwd=d, capture_output=True, text=True,
|
||||
timeout=60)
|
||||
issues = json.loads(p.stdout or "[]")
|
||||
except Exception: # noqa: BLE001 the hint is optional; the original SAP result stays
|
||||
return []
|
||||
finally:
|
||||
shutil.rmtree(d, ignore_errors=True)
|
||||
return [{"line": i.get("start", {}).get("row"), "severity": "E", "message": i.get("description", "")}
|
||||
for i in issues if i.get("key") == "parser_error"][:10]
|
||||
|
||||
|
||||
def augment(text, messages):
|
||||
"""Add the messages to a failed-write result (JSON object keeps valid JSON)."""
|
||||
if not messages:
|
||||
return text
|
||||
try:
|
||||
obj = json.loads(text)
|
||||
if isinstance(obj, dict):
|
||||
obj["syntaxCheck"] = {"source": "local abaplint (the rejected source, not stored in SAP)",
|
||||
"messages": messages}
|
||||
return json.dumps(obj)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return text + "\nSyntax check (local abaplint, rejected source): " + "; ".join(
|
||||
f"line {m['line']}: {m['message']}" for m in messages)
|
||||
Reference in New Issue
Block a user