Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/public-spiders-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@biomejs/biome": patch
---

Fixed [#7981](https://github.com/biomejs/biome/issues/7981)

Added support for `html.experimentalFullSupportEnabled` in Vue jsx/tsx scripts. Biome now supports `html.experimentalFullSupportEnabled` in Vue jsx/tsx scripts instead of throwing errors.
Comment thread
ematipico marked this conversation as resolved.
Outdated
115 changes: 115 additions & 0 deletions crates/biome_cli/tests/cases/handle_vue_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,121 @@ const props: Props = { title: "Hello" };
));
}

#[test]
fn full_support_tsx() {
let fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();

fs.insert(
"biome.json".into(),
r#"{ "html": { "formatter": {"enabled": true}, "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"#.as_bytes(),
);

let vue_file_path = Utf8Path::new("file.vue");
fs.insert(
vue_file_path.into(),
r#"<script lang="tsx">
import z from "zod";
import { sure } from "sure.js";
import s from "src/utils";

interface Props {
title: string;
}

let schema = z.object().optional();
schema + sure();
const props: Props = { title: "Hello" };

function FunctionalComponent() {
return <div></div>;
}

</script>

<template>
<div></div>
</template>

<style>
.class { background: red}
</style>
"#
.as_bytes(),
);

let (fs, result) = run_cli(
fs,
&mut console,
Args::from(["check", "--write", "--unsafe", vue_file_path.as_str()].as_slice()),
);

assert!(result.is_ok(), "run_cli returned {result:?}");

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"full_support_tsx",
fs,
console,
result,
));
}

#[test]
fn full_support_jsx() {
let fs = MemoryFileSystem::default();
let mut console = BufferConsole::default();

fs.insert(
"biome.json".into(),
r#"{ "html": { "formatter": {"enabled": true}, "linter": {"enabled": true}, "experimentalFullSupportEnabled": true } }"#.as_bytes(),
);

let vue_file_path = Utf8Path::new("file.vue");
fs.insert(
vue_file_path.into(),
r#"<script lang="jsx">
import z from "zod";
import { sure } from "sure.js";
import s from "src/utils";

let schema = z.object().optional();
schema + sure();

function FunctionalComponent() {
return <div></div>;
}

</script>

<template>
<div></div>
</template>

<style>
.class { background: red}
</style>
"#
.as_bytes(),
);

let (fs, result) = run_cli(
fs,
&mut console,
Args::from(["check", "--write", "--unsafe", vue_file_path.as_str()].as_slice()),
);

assert!(result.is_ok(), "run_cli returned {result:?}");

assert_cli_snapshot(SnapshotPayload::new(
module_path!(),
"full_support_jsx",
fs,
console,
result,
));
}

#[test]
fn format_stdin_successfully() {
let fs = MemoryFileSystem::default();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
source: crates/biome_cli/tests/snap_test.rs
expression: redactor(content)
---
## `biome.json`

```json
{
"html": {
"formatter": { "enabled": true },
"linter": { "enabled": true },
"experimentalFullSupportEnabled": true
}
}
```

## `file.vue`

```vue
<script lang="jsx">
import { sure } from "sure.js";
import z from "zod";

const schema = z.object().optional();
schema + sure();

function _FunctionalComponent() {
return <div></div>;
}
</script>

<template>
<div></div>
</template>

<style>
.class {
background: red;
}
</style>

```

# Emitted Messages

```block
Checked 1 file in <TIME>. Fixed 1 file.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
source: crates/biome_cli/tests/snap_test.rs
expression: redactor(content)
---
## `biome.json`

```json
{
"html": {
"formatter": { "enabled": true },
"linter": { "enabled": true },
"experimentalFullSupportEnabled": true
}
}
```

## `file.vue`

```vue
<script lang="tsx">
import { sure } from "sure.js";
import z from "zod";

interface Props {
title: string;
}

const schema = z.object().optional();
schema + sure();
const _props: Props = { title: "Hello" };

function _FunctionalComponent() {
return <div></div>;
}
</script>

<template>
<div></div>
</template>

<style>
.class {
background: red;
}
</style>

```

# Emitted Messages

```block
Checked 1 file in <TIME>. Fixed 1 file.
```
57 changes: 21 additions & 36 deletions crates/biome_html_syntax/src/element_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,59 +165,44 @@ impl HtmlElement {
name_token.text_trimmed().eq_ignore_ascii_case("script")
}

/// Returns `true` if the element is a `<script type="module">`
pub fn is_javascript_module(&self) -> SyntaxResult<bool> {
let is_script = self.is_script_tag();
let type_attribute = self.find_attribute_by_name("type");
let is_type_module = type_attribute.is_some_and(|attribute| {
fn has_attribute(&self, name: &str, value: &str) -> bool {
let attribute = self.find_attribute_by_name(name);
attribute.is_some_and(|attribute| {
attribute
.initializer()
.and_then(|initializer| initializer.value().ok())
.and_then(|value| value.as_html_string().cloned())
.and_then(|value| value.value_token().ok())
.is_some_and(|token| {
let text = inner_string_text(&token);
text.eq_ignore_ascii_case("module")
text.eq_ignore_ascii_case(value)
})
});
})
}

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

/// Returns `true` if the element is a `<script lang="ts">`
pub fn is_typescript_lang(&self) -> bool {
let is_script = self.is_script_tag();
let lang_attribute = self.find_attribute_by_name("lang");
let is_lang_typescript = lang_attribute.is_some_and(|attribute| {
attribute
.initializer()
.and_then(|initializer| initializer.value().ok())
.and_then(|value| value.as_html_string().cloned())
.and_then(|value| value.value_token().ok())
.is_some_and(|token| {
let text = inner_string_text(&token);
text.eq_ignore_ascii_case("ts")
})
});
is_script && is_lang_typescript
self.is_script_tag() && self.has_attribute("lang", "ts")
}

/// Returns `true` if the element is a `<script lang="jsx">`
pub fn is_jsx_lang(&self) -> bool {
self.is_script_tag() && self.has_attribute("lang", "jsx")
}

/// Returns `true` if the element is a `<script lang="tsx">`
pub fn is_tsx_lang(&self) -> bool {
self.is_script_tag() && self.has_attribute("lang", "tsx")
}

/// Returns `true` if the element is a `<style lang="sass">` or `<style lang="scss">`
pub fn is_sass_lang(&self) -> bool {
let is_style = self.is_style_tag();
let lang_attribute = self.find_attribute_by_name("lang");
let is_lang_typescript = lang_attribute.is_some_and(|attribute| {
attribute
.initializer()
.and_then(|initializer| initializer.value().ok())
.and_then(|value| value.as_html_string().cloned())
.and_then(|value| value.value_token().ok())
.is_some_and(|token| {
let text = inner_string_text(&token);
text.eq_ignore_ascii_case("sass") || text.eq_ignore_ascii_case("scss")
})
});
is_style && is_lang_typescript
self.is_style_tag() && self.has_attribute("lang", "scss")
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/biome_service/src/file_handlers/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,10 @@ pub(crate) fn parse_embedded_script(
let file_source = if html_file_source.is_svelte() || html_file_source.is_vue() {
let mut file_source = if element.is_typescript_lang() {
JsFileSource::ts()
} else if element.is_jsx_lang() {
JsFileSource::jsx()
} else if element.is_tsx_lang() {
JsFileSource::tsx()
} else {
JsFileSource::js_module()
};
Expand Down