test(anchor): harden JS helper extraction
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
90fd37dbd3
commit
770d877467
1 changed files with 122 additions and 9 deletions
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue