Conversation
|
WalkthroughThis PR refactors embedded-content parsing for HTML-like files by introducing an HtmlVariant-driven flow (Standard, Astro, Vue, Svelte) in parse_embedded_nodes. It changes parse_embedded_script to return a JsFileSource, adds parse_text_expression plus per-variant/single/double text-expression helpers (including parse_svelte_text_expression), and propagates JsFileSource through the embedding flow. The workspace server renamed a local binding from parse_embedded to parse_embedded_nodes. New CLI tests cover TypeScript handling in Svelte and Vue embedded expressions. Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
This does resolve the issue in svelte files, but not in Vue files.
Note the <script> tag does not have lang="ts" (errors on ts syntax): https://play.vuejs.org/#eNp9kE9PwzAMxb9K5PPUaYLTVCYB2gEOgIBjLlVnuo40iWJnFFX97nNS7c9h2i1+v+f42QM8el/sI8ISSqpD61kRcvQrbWtniZWNnXpQC23L+cSFSMHYeVMxSqVUuV2shiFbK1KV/VfjWM5FTF0XTpgBk3z70zbFjpyVoUPq11C7zrcGw7vnVsZqWKpMEquMcX+vWeMQcXbU6y3Wv1f0HfVJ0/ARkDDsUcOJcRUa5Amvv96wl/cJdm4TjbhvwE8kZ2LKONmeot1I7AtfTvvSeRe4tc03rXtGS8elUtDkHLNfg1z++cbq57h3xX3u03aE8QDfgZAO
I'll admit that this is a rather silly example, as nobody would actually write code like this, but it still demonstrates the issue.
But on the bright side, this resolves all the obvious issues in my work's svelte codebase 🥳 (there's a few more that appear valid on the outside but I need to investigate a bit before filing an issue)
|
@dyc3 addressed your comments, and added a few tests too |
9a89883 to
91ca16a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@crates/biome_service/src/file_handlers/html.rs`:
- Around line 1000-1007: The code currently hardcodes EmbeddingKind::Vue {
setup: false, is_source: false } when re-wrapping js_file_source, dropping any
existing `<script setup>` context; fix by reading the existing setup flag from
js_file_source (e.g., inspect its embedding kind or an accessor on JsFileSource)
and pass that boolean into with_embedding_kind so you construct
EmbeddingKind::Vue { setup: existing_setup, is_source: false } before calling
parse_text_expression; update the js_file_source -> file_source conversion that
precedes parse_text_expression to preserve the original setup value.
| js_file_source: JsFileSource, | ||
| ) -> Option<(EmbeddedSnippet<JsLanguage>, DocumentFileSource)> { | ||
| let expression = element.expression().ok()?; | ||
| let content = expression.html_literal_token().ok()?; | ||
| let file_source = JsFileSource::js_module().with_embedding_kind(EmbeddingKind::Vue { | ||
| let file_source = js_file_source.with_embedding_kind(EmbeddingKind::Vue { | ||
| setup: false, | ||
| is_source: false, | ||
| }); | ||
| let document_file_source = DocumentFileSource::Js(file_source); | ||
| let options = settings.parse_options::<JsLanguage>(biome_path, &document_file_source); | ||
| let parse = parse_js_with_offset_and_cache( | ||
| content.text(), | ||
| content.text_range().start(), | ||
| file_source, | ||
| options, | ||
| cache, | ||
| ); | ||
| let snippet = EmbeddedSnippet::new( | ||
| parse.into(), | ||
| expression.range(), | ||
| content.text_range(), | ||
| content.text_range().start(), | ||
| ); | ||
| Some((snippet, document_file_source)) | ||
| parse_text_expression(expression, cache, biome_path, settings, file_source) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n crates/biome_service/src/file_handlers/html.rs | sed -n '990,1020p'Repository: biomejs/biome
Length of output: 1246
🏁 Script executed:
wc -l crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 108
🏁 Script executed:
git ls-files crates/biome_service/src/file_handlers/ | head -20Repository: biomejs/biome
Length of output: 634
🏁 Script executed:
cat -n crates/biome_service/src/file_handlers/html.rs | head -50Repository: biomejs/biome
Length of output: 2838
🏁 Script executed:
rg "fn.*js_file_source.*EmbeddedSnippet" crates/biome_service/src/file_handlers/html.rs -A 10Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "parse_vue_template_expression" crates/biome_service/src/file_handlers/html.rs -B 5 -A 15Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "setup: false" crates/biome_service/src/file_handlers/html.rs -B 8 -A 2Repository: biomejs/biome
Length of output: 467
🏁 Script executed:
rg "EmbeddingKind::Vue" crates/biome_service/src/file_handlers/html.rs -B 3 -A 3Repository: biomejs/biome
Length of output: 726
🏁 Script executed:
rg "enum EmbeddingKind|struct EmbeddingKind" -A 10Repository: biomejs/biome
Length of output: 1500
🏁 Script executed:
rg "as_embedding_kind|with_embedding_kind" crates/biome_service/src/file_handlers/html.rs -B 2 -A 2Repository: biomejs/biome
Length of output: 1882
🏁 Script executed:
rg "impl.*JsFileSource|pub struct JsFileSource|pub enum JsFileSource" -A 20Repository: biomejs/biome
Length of output: 9853
🏁 Script executed:
rg "as_embedding_kind|with_embedding_kind" -B 2 -A 2 | head -100Repository: biomejs/biome
Length of output: 7948
🏁 Script executed:
rg "EmbeddingKind::Vue.*setup" -B 2 -A 2Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "setup: true|setup: false" -B 3 -A 3 | head -150Repository: biomejs/biome
Length of output: 2260
🏁 Script executed:
head -1100 crates/biome_service/src/file_handlers/html.rs | tail -200 | cat -nRepository: biomejs/biome
Length of output: 9011
🏁 Script executed:
grep -n "parse_vue_template_expression\|parse_text_expression\|EmbeddedSnippet" crates/biome_service/src/file_handlers/html.rs | head -20Repository: biomejs/biome
Length of output: 1276
🏁 Script executed:
sed -n '950,1100p' crates/biome_service/src/file_handlers/html.rs | cat -nRepository: biomejs/biome
Length of output: 6890
🏁 Script executed:
rg "parse_vue_template_expression|parse_text_expression" crates/biome_service/src/file_handlers/ -B 15 -A 2 | head -200Repository: biomejs/biome
Length of output: 6139
🏁 Script executed:
rg "fn parse_vue_template_expression|fn parse_text_expression|fn extract_vue_expression" crates/biome_service/src/file_handlers/html.rs -A 30Repository: biomejs/biome
Length of output: 1081
🏁 Script executed:
sed -n '960,1050p' crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 3359
🏁 Script executed:
rg -n "Option.*EmbeddedSnippet.*JsLanguage.*DocumentFileSource" crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 540
🏁 Script executed:
rg "js_file_source.with_embedding_kind" crates/biome_service/src/file_handlers/html.rs -B 10 -A 5Repository: biomejs/biome
Length of output: 649
🏁 Script executed:
ast-grep --pattern 'fn $FUNC($_) -> Option<(EmbeddedSnippet<$_>, $_)> { $$$}' crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 39
🏁 Script executed:
python3 <<'EOF'
with open('crates/biome_service/src/file_handlers/html.rs', 'r') as f:
lines = f.readlines()
for i, line in enumerate(lines[995:1015], start=996):
print(f"{i}: {line}", end='')
EOFRepository: biomejs/biome
Length of output: 940
🏁 Script executed:
python3 <<'EOF'
with open('crates/biome_service/src/file_handlers/html.rs', 'r') as f:
content = f.read()
# Find all occurrences of "setup: false"
import re
matches = list(re.finditer(r'setup: false', content))
for match in matches:
start = max(0, match.start() - 300)
end = min(len(content), match.end() + 300)
line_num = content[:match.start()].count('\n') + 1
print(f"\n=== Around line {line_num} ===")
print(content[start:end])
print("---")
EOFRepository: biomejs/biome
Length of output: 699
🏁 Script executed:
grep -n "setup: false" crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 83
🏁 Script executed:
sed -n '1000,1010p' crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 509
🏁 Script executed:
ls -la crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 153
🏁 Script executed:
rg "setup" crates/biome_service/src/file_handlers/html.rs | head -20Repository: biomejs/biome
Length of output: 147
🏁 Script executed:
rg "parse_text_expression.*js_file_source" -B 5 -A 5Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "EmbeddingKind::Vue.*setup.*false" -B 10 -A 5Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "pub enum EmbeddingKind|EmbeddingKind::" -A 3 | head -100Repository: biomejs/biome
Length of output: 7682
🏁 Script executed:
rg "with_embedding_kind" -B 5 -A 8Repository: biomejs/biome
Length of output: 18033
🏁 Script executed:
rg "as_embedding_kind" -B 2 -A 10Repository: biomejs/biome
Length of output: 16878
🏁 Script executed:
rg "fn as_embedding_kind|fn with_embedding_kind" -A 5Repository: biomejs/biome
Length of output: 1718
🏁 Script executed:
rg "EmbeddingKind::Vue.*setup" crates/ -B 2 -A 2 | head -150Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "script setup" crates/biome_service/src/file_handlers/ -B 3 -A 3Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "template.*expression|expression.*template" crates/biome_service/src/file_handlers/html.rs -B 2 -A 2 | head -100Repository: biomejs/biome
Length of output: 315
🏁 Script executed:
rg "setup.*true|setup.*false" crates/ -B 4 -A 2 | grep -A 4 -B 4 "Vue" | head -200Repository: biomejs/biome
Length of output: 2204
🏁 Script executed:
rg "EmbeddingKind::Vue.*{" crates/biome_service/ -B 5 -A 10 | head -250Repository: biomejs/biome
Length of output: 162
🏁 Script executed:
rg "pub enum JsFileSource|pub struct JsFileSource" -A 20Repository: biomejs/biome
Length of output: 1650
🏁 Script executed:
python3 <<'EOF'
import pathlib
p = pathlib.Path('crates/biome_service/src/file_handlers/html.rs')
if p.exists():
lines = p.read_text().splitlines()
print(f"Total lines: {len(lines)}")
if len(lines) > 1007:
for i in range(999, min(1008, len(lines))):
print(f"{i+1}: {lines[i]}")
else:
print("File has fewer than 1008 lines")
else:
print("File not found")
EOFRepository: biomejs/biome
Length of output: 492
🏁 Script executed:
find crates -name "html.rs" -type f | xargs wc -lRepository: biomejs/biome
Length of output: 315
🏁 Script executed:
rg "fn.*Option.*EmbeddedSnippet" crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 39
🏁 Script executed:
rg "^fn |^ fn " crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 1447
🏁 Script executed:
rg "parse_vue.*expression|vue.*template" crates/biome_service/src/file_handlers/html.rs -i | head -20Repository: biomejs/biome
Length of output: 153
🏁 Script executed:
rg "with_embedding_kind.*Vue" crates/ -B 3 -A 3 | head -200Repository: biomejs/biome
Length of output: 3653
🏁 Script executed:
rg "EmbeddingKind::Vue.*setup" crates/ -B 3 -A 3 | sort | uniq | head -100Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
web_search
Vue script setup context preservation in template expressionsRepository: biomejs/biome
Length of output: 149
🏁 Script executed:
python3 <<'EOF'
import re
with open('crates/biome_service/src/file_handlers/html.rs', 'r') as f:
content = f.read()
# Search for functions that return Option<(EmbeddedSnippet
pattern = r'fn\s+\w+\s*\([^)]*js_file_source[^)]*\)[^{]*Option\s*<\s*\(\s*EmbeddedSnippet'
matches = list(re.finditer(pattern, content))
print(f"Found {len(matches)} functions matching pattern\n")
for match in matches:
line_num = content[:match.start()].count('\n') + 1
print(f"Line {line_num}: {match.group()}\n")
# Also search for any with_embedding_kind calls
pattern2 = r'with_embedding_kind\s*\(\s*EmbeddingKind::Vue\s*\{'
for match in re.finditer(pattern2, content):
line_num = content[:match.start()].count('\n') + 1
start = max(0, match.start() - 200)
end = min(len(content), match.end() + 200)
print(f"\n=== with_embedding_kind call at line {line_num} ===")
print(content[start:end])
EOFRepository: biomejs/biome
Length of output: 1300
🏁 Script executed:
grep -n "with_embedding_kind.*EmbeddingKind::Vue" crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 226
🏁 Script executed:
sed -n '990,1020p' crates/biome_service/src/file_handlers/html.rs | cat -nRepository: biomejs/biome
Length of output: 1246
🏁 Script executed:
head -1020 crates/biome_service/src/file_handlers/html.rs | tail -40 | cat -nRepository: biomejs/biome
Length of output: 1527
🏁 Script executed:
git log --oneline -20 -- crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 76
🏁 Script executed:
git show HEAD:crates/biome_service/src/file_handlers/html.rs | sed -n '1000,1010p'Repository: biomejs/biome
Length of output: 509
🏁 Script executed:
git diff HEAD crates/biome_service/src/file_handlers/html.rs | head -100Repository: biomejs/biome
Length of output: 39
🏁 Script executed:
git status crates/biome_service/src/file_handlers/html.rsRepository: biomejs/biome
Length of output: 122
🏁 Script executed:
python3 -c "
with open('crates/biome_service/src/file_handlers/html.rs') as f:
lines = f.readlines()
print('Total lines:', len(lines))
print()
for i in range(999, min(1010, len(lines))):
print(f'{i+1:4d}: {lines[i]}', end='')
"Repository: biomejs/biome
Length of output: 594
Preserve Vue setup context when re-wrapping the embedding kind.
Template expressions in Vue files with <script setup> need to maintain that context for correct variable resolution. Hardcoding setup: false drops this information even when the incoming js_file_source has setup: true, preventing proper scope analysis for setup bindings.
Extract the existing setup flag before re-wrapping:
Suggested fix
- let file_source = js_file_source.with_embedding_kind(EmbeddingKind::Vue {
- setup: false,
- is_source: false,
- });
+ let setup = matches!(
+ js_file_source.as_embedding_kind(),
+ EmbeddingKind::Vue { setup: true, .. }
+ );
+ let file_source = js_file_source.with_embedding_kind(EmbeddingKind::Vue {
+ setup,
+ is_source: false,
+ });🤖 Prompt for AI Agents
In `@crates/biome_service/src/file_handlers/html.rs` around lines 1000 - 1007, The
code currently hardcodes EmbeddingKind::Vue { setup: false, is_source: false }
when re-wrapping js_file_source, dropping any existing `<script setup>` context;
fix by reading the existing setup flag from js_file_source (e.g., inspect its
embedding kind or an accessor on JsFileSource) and pass that boolean into
with_embedding_kind so you construct EmbeddingKind::Vue { setup: existing_setup,
is_source: false } before calling parse_text_expression; update the
js_file_source -> file_source conversion that precedes parse_text_expression to
preserve the original setup value.
|
There's still an issue where a user could create multiple scripts that have different languages. Worth considering a rule for that |
Summary
Closes #8983
Slightly refactored how snippets are parsed. Now they are parsed based on the embedded kind, so that we surgically understand when the
JsFileSourceneeds to change.Test Plan
Added a new test
Docs