Skip to content

Commit 3256f82

Browse files
lisiurematipico
andauthored
feat: support html.experimentalFullSupportEnabled for vue with jsx/tsx script (#7986)
Co-authored-by: Emanuele Stoppa <[email protected]>
1 parent 4855c4a commit 3256f82

File tree

6 files changed

+246
-36
lines changed

6 files changed

+246
-36
lines changed

.changeset/public-spiders-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#7981](https://github.com/biomejs/biome/issues/7981). Now Biome correctly detects and parses `lang='tsx'` and `lang='jsx'` languages when used inside in `.vue` files, when `.experimentalFullSupportEnabled` is enabled.

crates/biome_cli/tests/cases/handle_vue_files.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,121 @@ const props: Props = { title: "Hello" };
568568
));
569569
}
570570

571+
#[test]
572+
fn full_support_tsx() {
573+
let fs = MemoryFileSystem::default();
574+
let mut console = BufferConsole::default();
575+
576+
fs.insert(
577+
"biome.json".into(),
578+
r#"{ "html": { "formatter": {"enabled": true}, "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"#.as_bytes(),
579+
);
580+
581+
let vue_file_path = Utf8Path::new("file.vue");
582+
fs.insert(
583+
vue_file_path.into(),
584+
r#"<script lang="tsx">
585+
import z from "zod";
586+
import { sure } from "sure.js";
587+
import s from "src/utils";
588+
589+
interface Props {
590+
title: string;
591+
}
592+
593+
let schema = z.object().optional();
594+
schema + sure();
595+
const props: Props = { title: "Hello" };
596+
597+
function FunctionalComponent() {
598+
return <div></div>;
599+
}
600+
601+
</script>
602+
603+
<template>
604+
<div></div>
605+
</template>
606+
607+
<style>
608+
.class { background: red}
609+
</style>
610+
"#
611+
.as_bytes(),
612+
);
613+
614+
let (fs, result) = run_cli(
615+
fs,
616+
&mut console,
617+
Args::from(["check", "--write", "--unsafe", vue_file_path.as_str()].as_slice()),
618+
);
619+
620+
assert!(result.is_ok(), "run_cli returned {result:?}");
621+
622+
assert_cli_snapshot(SnapshotPayload::new(
623+
module_path!(),
624+
"full_support_tsx",
625+
fs,
626+
console,
627+
result,
628+
));
629+
}
630+
631+
#[test]
632+
fn full_support_jsx() {
633+
let fs = MemoryFileSystem::default();
634+
let mut console = BufferConsole::default();
635+
636+
fs.insert(
637+
"biome.json".into(),
638+
r#"{ "html": { "formatter": {"enabled": true}, "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"#.as_bytes(),
639+
);
640+
641+
let vue_file_path = Utf8Path::new("file.vue");
642+
fs.insert(
643+
vue_file_path.into(),
644+
r#"<script lang="jsx">
645+
import z from "zod";
646+
import { sure } from "sure.js";
647+
import s from "src/utils";
648+
649+
let schema = z.object().optional();
650+
schema + sure();
651+
652+
function FunctionalComponent() {
653+
return <div></div>;
654+
}
655+
656+
</script>
657+
658+
<template>
659+
<div></div>
660+
</template>
661+
662+
<style>
663+
.class { background: red}
664+
</style>
665+
"#
666+
.as_bytes(),
667+
);
668+
669+
let (fs, result) = run_cli(
670+
fs,
671+
&mut console,
672+
Args::from(["check", "--write", "--unsafe", vue_file_path.as_str()].as_slice()),
673+
);
674+
675+
assert!(result.is_ok(), "run_cli returned {result:?}");
676+
677+
assert_cli_snapshot(SnapshotPayload::new(
678+
module_path!(),
679+
"full_support_jsx",
680+
fs,
681+
console,
682+
result,
683+
));
684+
}
685+
571686
#[test]
572687
fn format_stdin_successfully() {
573688
let fs = MemoryFileSystem::default();
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
source: crates/biome_cli/tests/snap_test.rs
3+
expression: redactor(content)
4+
---
5+
## `biome.json`
6+
7+
```json
8+
{
9+
"html": {
10+
"formatter": { "enabled": true },
11+
"linter": { "enabled": true },
12+
"experimentalFullSupportEnabled": true
13+
}
14+
}
15+
```
16+
17+
## `file.vue`
18+
19+
```vue
20+
<script lang="jsx">
21+
import { sure } from "sure.js";
22+
import z from "zod";
23+
24+
const schema = z.object().optional();
25+
schema + sure();
26+
27+
function _FunctionalComponent() {
28+
return <div></div>;
29+
}
30+
</script>
31+
32+
<template>
33+
<div></div>
34+
</template>
35+
36+
<style>
37+
.class {
38+
background: red;
39+
}
40+
</style>
41+
42+
```
43+
44+
# Emitted Messages
45+
46+
```block
47+
Checked 1 file in <TIME>. Fixed 1 file.
48+
```
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
source: crates/biome_cli/tests/snap_test.rs
3+
expression: redactor(content)
4+
---
5+
## `biome.json`
6+
7+
```json
8+
{
9+
"html": {
10+
"formatter": { "enabled": true },
11+
"linter": { "enabled": true },
12+
"experimentalFullSupportEnabled": true
13+
}
14+
}
15+
```
16+
17+
## `file.vue`
18+
19+
```vue
20+
<script lang="tsx">
21+
import { sure } from "sure.js";
22+
import z from "zod";
23+
24+
interface Props {
25+
title: string;
26+
}
27+
28+
const schema = z.object().optional();
29+
schema + sure();
30+
const _props: Props = { title: "Hello" };
31+
32+
function _FunctionalComponent() {
33+
return <div></div>;
34+
}
35+
</script>
36+
37+
<template>
38+
<div></div>
39+
</template>
40+
41+
<style>
42+
.class {
43+
background: red;
44+
}
45+
</style>
46+
47+
```
48+
49+
# Emitted Messages
50+
51+
```block
52+
Checked 1 file in <TIME>. Fixed 1 file.
53+
```

crates/biome_html_syntax/src/element_ext.rs

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -165,59 +165,44 @@ impl HtmlElement {
165165
name_token.text_trimmed().eq_ignore_ascii_case("script")
166166
}
167167

168-
/// Returns `true` if the element is a `<script type="module">`
169-
pub fn is_javascript_module(&self) -> SyntaxResult<bool> {
170-
let is_script = self.is_script_tag();
171-
let type_attribute = self.find_attribute_by_name("type");
172-
let is_type_module = type_attribute.is_some_and(|attribute| {
168+
fn has_attribute(&self, name: &str, value: &str) -> bool {
169+
let attribute = self.find_attribute_by_name(name);
170+
attribute.is_some_and(|attribute| {
173171
attribute
174172
.initializer()
175173
.and_then(|initializer| initializer.value().ok())
176174
.and_then(|value| value.as_html_string().cloned())
177175
.and_then(|value| value.value_token().ok())
178176
.is_some_and(|token| {
179177
let text = inner_string_text(&token);
180-
text.eq_ignore_ascii_case("module")
178+
text.eq_ignore_ascii_case(value)
181179
})
182-
});
180+
})
181+
}
183182

184-
Ok(is_script && is_type_module)
183+
/// Returns `true` if the element is a `<script type="module">`
184+
pub fn is_javascript_module(&self) -> SyntaxResult<bool> {
185+
Ok(self.is_script_tag() && self.has_attribute("type", "module"))
185186
}
186187

187188
/// Returns `true` if the element is a `<script lang="ts">`
188189
pub fn is_typescript_lang(&self) -> bool {
189-
let is_script = self.is_script_tag();
190-
let lang_attribute = self.find_attribute_by_name("lang");
191-
let is_lang_typescript = lang_attribute.is_some_and(|attribute| {
192-
attribute
193-
.initializer()
194-
.and_then(|initializer| initializer.value().ok())
195-
.and_then(|value| value.as_html_string().cloned())
196-
.and_then(|value| value.value_token().ok())
197-
.is_some_and(|token| {
198-
let text = inner_string_text(&token);
199-
text.eq_ignore_ascii_case("ts")
200-
})
201-
});
202-
is_script && is_lang_typescript
190+
self.is_script_tag() && self.has_attribute("lang", "ts")
191+
}
192+
193+
/// Returns `true` if the element is a `<script lang="jsx">`
194+
pub fn is_jsx_lang(&self) -> bool {
195+
self.is_script_tag() && self.has_attribute("lang", "jsx")
196+
}
197+
198+
/// Returns `true` if the element is a `<script lang="tsx">`
199+
pub fn is_tsx_lang(&self) -> bool {
200+
self.is_script_tag() && self.has_attribute("lang", "tsx")
203201
}
204202

205203
/// Returns `true` if the element is a `<style lang="sass">` or `<style lang="scss">`
206204
pub fn is_sass_lang(&self) -> bool {
207-
let is_style = self.is_style_tag();
208-
let lang_attribute = self.find_attribute_by_name("lang");
209-
let is_lang_typescript = lang_attribute.is_some_and(|attribute| {
210-
attribute
211-
.initializer()
212-
.and_then(|initializer| initializer.value().ok())
213-
.and_then(|value| value.as_html_string().cloned())
214-
.and_then(|value| value.value_token().ok())
215-
.is_some_and(|token| {
216-
let text = inner_string_text(&token);
217-
text.eq_ignore_ascii_case("sass") || text.eq_ignore_ascii_case("scss")
218-
})
219-
});
220-
is_style && is_lang_typescript
205+
self.is_style_tag() && self.has_attribute("lang", "scss")
221206
}
222207
}
223208

crates/biome_service/src/file_handlers/html.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,10 @@ pub(crate) fn parse_embedded_script(
479479
let file_source = if html_file_source.is_svelte() || html_file_source.is_vue() {
480480
let mut file_source = if element.is_typescript_lang() {
481481
JsFileSource::ts()
482+
} else if element.is_jsx_lang() {
483+
JsFileSource::jsx()
484+
} else if element.is_tsx_lang() {
485+
JsFileSource::tsx()
482486
} else {
483487
JsFileSource::js_module()
484488
};

0 commit comments

Comments
 (0)