test(anchor): harden JS helper extraction

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Frank Song 2026-06-28 15:32:45 +08:00
parent 90fd37dbd3
commit 770d877467

View file

@ -44,12 +44,20 @@ def _run_node_script(script: str) -> str:
node = shutil.which("node")
if not node:
pytest.skip("node executable is required for JavaScript behavior checks")
result = subprocess.run(
[node, "-e", script],
cwd=ROOT,
text=True,
capture_output=True,
)
try:
result = subprocess.run(
[node, "-e", script],
cwd=ROOT,
text=True,
capture_output=True,
timeout=10,
)
except subprocess.TimeoutExpired as exc:
pytest.fail(
"node behavior check timed out"
f"\nstdout:\n{exc.stdout or '<empty>'}"
f"\nstderr:\n{exc.stderr or '<empty>'}",
)
if result.returncode:
pytest.fail(
"node behavior check failed"
@ -60,6 +68,54 @@ def _run_node_script(script: str) -> str:
return result.stdout.strip()
def _is_js_identifier_char(char: str) -> bool:
return char.isalnum() or char in {"_", "$"}
def _previous_significant_js_token(src: str, idx: int) -> str:
idx -= 1
while idx >= 0 and src[idx].isspace():
idx -= 1
if idx < 0:
return ""
if _is_js_identifier_char(src[idx]):
end = idx + 1
while idx >= 0 and _is_js_identifier_char(src[idx]):
idx -= 1
return src[idx + 1 : end]
return src[idx]
def _looks_like_js_regex_literal_start(src: str, idx: int) -> bool:
if src[idx] != "/" or src.startswith(("//", "/*"), idx):
return False
previous = _previous_significant_js_token(src, idx)
if previous in {"return", "throw", "case", "delete", "typeof", "void", "yield"}:
return True
return previous in {"", "(", "[", "{", "=", ":", ",", ";", "!", "?", "&", "|"}
def _skip_js_regex_literal(src: str, idx: int) -> int:
assert src[idx] == "/", f"expected regex literal at {idx}"
idx += 1
in_class = False
while idx < len(src):
if src[idx] == "\\":
idx += 2
continue
if src[idx] == "[":
in_class = True
elif src[idx] == "]":
in_class = False
elif src[idx] == "/" and not in_class:
idx += 1
while idx < len(src) and src[idx].isalpha():
idx += 1
return idx
idx += 1
raise AssertionError("JavaScript regex literal did not close")
def _skip_js_string_or_comment(src: str, idx: int) -> int:
if src.startswith("//", idx):
end = src.find("\n", idx + 2)
@ -68,6 +124,8 @@ def _skip_js_string_or_comment(src: str, idx: int) -> int:
end = src.find("*/", idx + 2)
assert end != -1, "JavaScript block comment did not close"
return end + 2
if _looks_like_js_regex_literal_start(src, idx):
return _skip_js_regex_literal(src, idx)
quote = src[idx]
if quote == "`":
return _skip_js_template_literal(src, idx)
@ -120,10 +178,46 @@ def _matching_delimiter(src: str, open_idx: int, opener: str, closer: str) -> in
raise AssertionError(f"{opener}{closer} delimiter did not close")
def _skip_js_whitespace(src: str, idx: int) -> int:
while idx < len(src) and src[idx].isspace():
idx += 1
return idx
def _find_function_declaration(src: str, name: str) -> tuple[int, int]:
idx = 0
while idx < len(src):
next_idx = _skip_js_string_or_comment(src, idx)
if next_idx != idx:
idx = next_idx
continue
if not src.startswith("function", idx):
idx += 1
continue
before = src[idx - 1] if idx else ""
after_keyword = idx + len("function")
after = src[after_keyword] if after_keyword < len(src) else ""
if _is_js_identifier_char(before) or _is_js_identifier_char(after):
idx += 1
continue
name_start = _skip_js_whitespace(src, after_keyword)
name_end = name_start + len(name)
if src[name_start:name_end] != name:
idx += 1
continue
after_name = src[name_end] if name_end < len(src) else ""
if _is_js_identifier_char(after_name):
idx += 1
continue
params_open = _skip_js_whitespace(src, name_end)
if params_open < len(src) and src[params_open] == "(":
return idx, params_open
idx += 1
raise AssertionError(f"{name} not found")
def _function_source(src: str, name: str) -> str:
start = src.find(f"function {name}")
assert start != -1, f"{name} not found"
params_open = src.find("(", start)
start, params_open = _find_function_declaration(src, name)
assert params_open != -1, f"{name} params not found"
params_close = _matching_delimiter(src, params_open, "(", ")")
brace = src.find("{", params_close)
@ -164,6 +258,25 @@ def test_function_extractor_handles_nested_template_literal_interpolation():
assert "function afterSample" not in body
def test_function_extractor_matches_exact_declarations_outside_comments():
source = """
// function sample(){ return 'comment'; }
function samplePrefix(){ return 'prefix'; }
function sample(){
const hasBrace = /\\{[^}]+\\}/.test(text);
return hasBrace ? 'target' : 'fallback';
}
function afterSample(){ return false; }
"""
body = _function_body(source, "sample")
assert "return hasBrace ? 'target' : 'fallback';" in body
assert "return 'comment';" not in body
assert "return 'prefix';" not in body
assert "function afterSample" not in body
def test_transparent_raw_content_helper_is_fallback_only_when_anchor_scene_absent():
helper = _function_body(_ui_js(), "_transparentStreamOrderedParts")