#!/bin/sh
# Names each method a patch adds that could be checked on its own, and
# says whether a test file changed in the same patch.
#
# devlog/devscripts/check_pure_logic.sh
#
# WHY THIS EXISTS. The process file asks that pure logic be pulled out
# so it can be checked on its own. Twice I wrote in a delivery that a
# new method was one input and one output, and wrote no case for it:
# emojiGroups, and the method building an ffmpeg option string. Noticing
# is not the hard part; noticing at the moment the patch is cut is. This
# looks for a method a patch adds whose body reads no property, calls
# nothing on the object, and touches no file or database, which is the
# shape a case can stand behind with no test set-up at all.
#
# It warns rather than refuses, since such a method may be covered by a
# case on the method that calls it.
set -u
WORK=${WORK:-/home/claude/work}
cd "$WORK" || exit 2
python3 - <<'PYEOF'
import re
import subprocess
changed = subprocess.run(["git", "diff", "confirmed-head..HEAD",
"--name-only"], capture_output=True, text=True).stdout.split()
tests_touched = [one for one in changed if one.startswith("tests/")]
reaches_out = re.compile(r"(file_|fopen|curl_|->db|->query|exec\(|"
r"unlink|mkdir|require|tl\()")
found = []
for path in changed:
if not path.startswith("src/") or not path.endswith(".php"):
continue
was = subprocess.run(["git", "show", "confirmed-head:" + path],
capture_output=True, text=True)
older = set()
if was.returncode == 0:
older = set(re.findall(r"function\s+(\w+)", was.stdout))
try:
text = open(path, encoding="utf-8").read()
except OSError:
continue
for m in re.finditer(r"function\s+(\w+)\s*\([^)]*\)[^{]*\{", text):
name = m.group(1)
if name in older:
continue
start = m.end() - 1
depth = 0
at = start
while at < len(text):
if text[at] == "{":
depth += 1
elif text[at] == "}":
depth -= 1
if depth == 0:
break
at += 1
body = text[start:at]
if "$this" in body or "::" in body:
continue
if reaches_out.search(body):
continue
if len(body.split("\n")) < 4:
continue
found.append((path, name))
if not found:
print("check: the patch adds no method that stands on its own")
else:
for path, name in found:
print("%s: %s reads nothing outside itself" % (path, name))
if tests_touched:
print("check: %d such method(s); the patch also changes %s" %
(len(found), ", ".join(tests_touched)))
else:
print("check: %d such method(s) and no test file changed. Each "
"could carry a case." % len(found))
PYEOF