From 770d8774670523e1ecaa2a07f8e25ec8f57d95ca Mon Sep 17 00:00:00 2001 From: Frank Song Date: Sun, 28 Jun 2026 15:32:45 +0800 Subject: [PATCH] test(anchor): harden JS helper extraction Co-Authored-By: Paperclip --- tests/test_anchor_fallback_ownership.py | 131 ++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 9 deletions(-) diff --git a/tests/test_anchor_fallback_ownership.py b/tests/test_anchor_fallback_ownership.py index d55ff055..627579ff 100644 --- a/tests/test_anchor_fallback_ownership.py +++ b/tests/test_anchor_fallback_ownership.py @@ -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 ''}" + f"\nstderr:\n{exc.stderr or ''}", + ) 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")