fix(extract): end a Svelte script block's trailing comment and report read errors

Review follow-ups on #3928:

- A `//` comment closing one `<script>` block swallowed a second block on
  the same line, since masking leaves them on one line. Terminate each
  block with U+2028 then `;`: the line terminator ends the comment and
  the `;` ends a statement that had none. Line numbers and byte offsets
  are unchanged.
- An unreadable file returns the `error` key again instead of an empty
  result, matching the behavior before the masking change.
This commit is contained in:
Agnik47 committed 2026-10-03 13:20:08 +05:30
1 parent b64806924e
commit 5de47e3df3
2 files changed
+41 -6

No files matched your search

+9 -6
View File
@@ -2387,10 +2387,13 @@ def _svelte_mask_non_script(src: str) -> tuple[str, str | None]:
continue
start, end = m.start(2), m.end(2)
chars[start:end] = src[start:end]
# Terminate the region in place of the following `<`, so two scripts on
# one line don't run together into a single statement.
if end < len(chars) and chars[end] == " ":
chars[end] = ";"
# Terminate the region in place of the closing `</script`, so two scripts
# on one line don't run together. U+2028 is a JS line terminator, so it
# ends a trailing `//` comment that would otherwise swallow the next
# block; the `;` after it then ends the statement (inside the comment it
# would be inert). U+2028 is not a newline, so line numbers hold, and its
# 3 UTF-8 bytes replace 3 ASCII ones, so byte offsets do too.
chars[end:end + 4] = ["\u2028", ";", "", ""]
if lang is None:
lang_m = _SCRIPT_LANG_RE.search(m.group(1))
if lang_m:
@@ -2421,8 +2424,8 @@ def extract_svelte(path: Path) -> dict:
"""
try:
src = path.read_text(encoding="utf-8", errors="replace")
except OSError:
return {"nodes": [], "edges": []}
except OSError as e:
return {"nodes": [], "edges": [], "error": str(e)}
masked, lang = _svelte_mask_non_script(src)
config = _JS_CONFIG if lang in ("js", "jsx") else _TS_CONFIG
+32
View File
@@ -164,6 +164,38 @@ def test_extract_svelte_scripts_on_one_line_do_not_merge(tmp_path):
assert "b()" in _labels(result)
def test_extract_svelte_line_comment_does_not_swallow_next_script(tmp_path):
"""A `//` comment ending one block must not hide a block on the same line."""
component = _write(
tmp_path / "src/Commented.svelte",
"<script module>// shared</script><script>function b() {}</script>\n"
"<script>function c() {}</script>\n",
)
result = extract_svelte(component)
assert result.get("parse_errors") is None
labels = _labels(result)
assert labels.get("b()") == "L1"
assert labels.get("c()") == "L2"
def test_extract_svelte_comment_after_unterminated_statement(tmp_path):
"""A statement with no `;` before the `//` comment still ends at the block."""
component = _write(
tmp_path / "src/Unterminated.svelte",
"<script module>const a = 1 // shared</script><script>function b() {}</script>\n",
)
result = extract_svelte(component)
assert result.get("parse_errors") is None
assert _labels(result).get("b()") == "L1"
def test_extract_svelte_unreadable_file_reports_error(tmp_path):
"""A file that cannot be read is reported, not returned as an empty result."""
result = extract_svelte(tmp_path / "missing" / "Gone.svelte")
assert result["nodes"] == [] and result["edges"] == []
assert "error" in result
def test_extract_svelte_markup_only_component_does_not_crash(tmp_path):
"""A `.svelte` file need not have a `<script>` block at all."""
component = _write(tmp_path / "src/Plain.svelte", "<h1>no script here</h1>\n")