- Introduced a new section for the Jinkendo Foundation, detailing design principles for the product family. - Updated README files to include references to the new design principles documentation. - Enhanced the overall documentation structure to improve navigation and accessibility of design resources. - Ensured consistency across documentation related to the Jinkendo Foundation and its principles.
89 lines
2.6 KiB
Python
89 lines
2.6 KiB
Python
"""Audit: INSERT %s-Platzhalter vs. Parameter-Anzahl in backend/."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _count_tuple_elements(node: ast.AST) -> int | None:
|
|
if isinstance(node, ast.Tuple):
|
|
return len(node.elts)
|
|
if isinstance(node, ast.List):
|
|
return len(node.elts)
|
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
|
|
if node.func.attr in {"values", "keys"}:
|
|
return None
|
|
if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Add):
|
|
left = _count_tuple_elements(node.left)
|
|
right = _count_tuple_elements(node.right)
|
|
if left is not None and right is not None:
|
|
return left + right
|
|
if isinstance(node, ast.ListComp):
|
|
return None
|
|
return None
|
|
|
|
|
|
def audit_file(path: Path) -> list[str]:
|
|
issues: list[str] = []
|
|
try:
|
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
except SyntaxError:
|
|
return issues
|
|
|
|
for node in ast.walk(tree):
|
|
if not isinstance(node, ast.Call):
|
|
continue
|
|
func = node.func
|
|
if not (
|
|
isinstance(func, ast.Attribute)
|
|
and func.attr == "execute"
|
|
and isinstance(func.value, ast.Name)
|
|
and func.value.id == "cur"
|
|
):
|
|
continue
|
|
if len(node.args) < 2:
|
|
continue
|
|
sql_node, params_node = node.args[0], node.args[1]
|
|
sql = None
|
|
if isinstance(sql_node, ast.Constant) and isinstance(sql_node.value, str):
|
|
sql = sql_node.value
|
|
elif isinstance(sql_node, ast.JoinedStr):
|
|
continue
|
|
if not sql or "INSERT" not in sql.upper():
|
|
continue
|
|
if "VALUES" not in sql.upper():
|
|
continue
|
|
ph = sql.count("%s")
|
|
param_count = _count_tuple_elements(params_node)
|
|
if param_count is None:
|
|
continue
|
|
if ph != param_count:
|
|
rel = path.relative_to(ROOT)
|
|
issues.append(
|
|
f"{rel}:{node.lineno} INSERT placeholders={ph} params={param_count}"
|
|
)
|
|
return issues
|
|
|
|
|
|
def main() -> int:
|
|
issues: list[str] = []
|
|
for path in ROOT.rglob("*.py"):
|
|
if "venv" in path.parts or "__pycache__" in path.parts:
|
|
continue
|
|
issues.extend(audit_file(path))
|
|
if issues:
|
|
print("MISMATCHES:")
|
|
for issue in sorted(issues):
|
|
print(issue)
|
|
return 1
|
|
print("OK: keine INSERT-Platzhalter-Mismatches gefunden (statisch prüfbar).")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|