Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/expose-html-parser-vue-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": minor
---

Exposed the `html.parser.vue` configuration option. This option enables parsing of Vue syntax (directives like `v-if`, `v-bind`, etc.) in `.html` files. Most Vue users don't need to enable this option since Vue files typically use the `.vue` extension, but it can be useful for projects that embed Vue syntax in regular HTML files.
86 changes: 86 additions & 0 deletions crates/biome_cli/tests/cases/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,92 @@ fn no_undeclared_classes_silent_without_style_info() {
));
}

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

let html_file = Utf8Path::new("file.html");
fs.insert(
html_file.into(),
r#"<div v-if="show">{{ message }}</div>
"#
.as_bytes(),
);
fs.insert(
Utf8Path::new("biome.json").into(),
r#"{
"html": {
"linter": {
"enabled": true
}
}
}"#
.as_bytes(),
);

let (fs, result) = run_cli(
fs,
&mut console,
Args::from(["lint", html_file.as_str()].as_slice()),
);

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

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

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

let html_file = Utf8Path::new("file.html");
fs.insert(
html_file.into(),
r#"<div v-if="show">{{ message }}</div>
"#
.as_bytes(),
);
fs.insert(
Utf8Path::new("biome.json").into(),
r#"{
"html": {
"parser": {
"vue": true,
"interpolation": true
},
"linter": {
"enabled": true
}
}
}"#
.as_bytes(),
);

let (fs, result) = run_cli(
fs,
&mut console,
Args::from(["lint", html_file.as_str()].as_slice()),
);

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

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

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

```json
{
"html": {
"linter": {
"enabled": true
}
}
}
```

## `file.html`

```html
<div v-if="show">{{ message }}</div>

```

# Termination Message

```block
lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Some errors were emitted while running checks.



```

# Emitted Messages

```block
file.html:1:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Vue syntax isn't enabled. Is this supposed to be a .vue file?

> 1 │ <div v-if="show">{{ message }}</div>
│ ^^^^^^^^^^^
2 │

i Remove it or enable the parsing using the html.parser.vue option.


```

```block
file.html:1:18 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

× Text expressions aren't supported.

> 1 │ <div v-if="show">{{ message }}</div>
│ ^^^^^^^^^^^^^
2 │

i Remove it or enable the parsing using the html.parser.interpolation option.


```

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

```json
{
"html": {
"parser": {
"vue": true,
"interpolation": true
},
"linter": {
"enabled": true
}
}
}
```

## `file.html`

```html
<div v-if="show">{{ message }}</div>

```

# Emitted Messages

```block
Checked 1 file in <TIME>. No fixes applied.
```
8 changes: 8 additions & 0 deletions crates/biome_configuration/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub type HtmlFormatterEnabled = Bool<false>; // Keep it disabled by default whil
pub type HtmlLinterEnabled = Bool<true>;
pub type HtmlAssistEnabled = Bool<true>;
pub type HtmlParseInterpolation = Bool<false>;
pub type HtmlParseVue = Bool<false>;

/// Options that changes how the HTML parser behaves
#[derive(
Expand All @@ -58,6 +59,13 @@ pub type HtmlParseInterpolation = Bool<false>;
pub struct HtmlParserConfiguration {
/// Enables the parsing of double text expressions such as `{{ expression }}` inside `.html` files
pub interpolation: Option<HtmlParseInterpolation>,

/// Enables parsing of Vue syntax (v-if, v-bind, etc.) in `.html` files.
///
/// Biome will already automatically enable Vue parsing in `.vue` files, so you probably don't want
/// to enable this option. This only affects `.html` files, and does not change how `.vue`, `.svelte`,
/// or `.astro` files are parsed.
pub vue: Option<HtmlParseVue>,
}

/// Options that changes how the HTML formatter behaves
Expand Down
4 changes: 4 additions & 0 deletions crates/biome_html_parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ impl HtmlParserOptions {
self
}

pub fn set_vue(&mut self, value: bool) {
self.vue = value;
}

pub fn with_svelte(mut self) -> Self {
self.svelte = true;
self
Expand Down
8 changes: 7 additions & 1 deletion crates/biome_service/src/file_handlers/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ use crate::{
use biome_analyze::{AnalysisFilter, AnalyzerConfiguration, AnalyzerOptions, ControlFlow, Never};
use biome_configuration::html::{
HtmlAssistConfiguration, HtmlAssistEnabled, HtmlFormatterConfiguration, HtmlFormatterEnabled,
HtmlLinterConfiguration, HtmlLinterEnabled, HtmlParseInterpolation, HtmlParserConfiguration,
HtmlLinterConfiguration, HtmlLinterEnabled, HtmlParseInterpolation, HtmlParseVue,
HtmlParserConfiguration,
};
use biome_css_parser::{CssModulesKind, parse_css_with_offset_and_cache};
use biome_css_syntax::{
Expand Down Expand Up @@ -72,12 +73,14 @@ use tracing::{debug_span, error, instrument, trace_span};
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct HtmlParserSettings {
pub interpolation: Option<HtmlParseInterpolation>,
pub vue: Option<HtmlParseVue>,
}

impl From<HtmlParserConfiguration> for HtmlParserSettings {
fn from(configuration: HtmlParserConfiguration) -> Self {
Self {
interpolation: configuration.interpolation,
vue: configuration.vue,
}
}
}
Expand Down Expand Up @@ -326,6 +329,9 @@ impl ServiceLanguage for HtmlLanguage {
if language.interpolation.unwrap_or_default().into() && html_file_source.is_html() {
options = options.with_double_text_expression();
}
if language.vue.unwrap_or_default().into() && html_file_source.is_html() {
options = options.with_vue();
}

overrides.apply_override_html_parser_options(path, &mut options);

Expand Down
5 changes: 5 additions & 0 deletions crates/biome_service/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,11 @@ impl OverrideSettingPattern {
if let Some(interpolation) = html_parser.interpolation {
options.set_double_text_expression(interpolation.value());
}
if options.is_html() {
if let Some(vue) = html_parser.vue {
options.set_vue(vue.value());
}
}
}

fn apply_overrides_to_css_parser_options(&self, options: &mut CssParserOptions) {
Expand Down
78 changes: 78 additions & 0 deletions crates/biome_service/src/settings.tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::settings::{
use crate::workspace::DocumentFileSource;
use biome_analyze::RuleFilter;
use biome_configuration::analyzer::{GroupPlainConfiguration, SeverityOrGroup, Style};
use biome_configuration::html::{HtmlConfiguration, HtmlParserConfiguration};
use biome_configuration::javascript::JsxRuntime;
use biome_configuration::json::{JsonAssistConfiguration, JsonLinterConfiguration};
use biome_configuration::max_size::MaxSize;
Expand All @@ -15,6 +16,8 @@ use biome_configuration::{
Overrides, RuleConfiguration, RulePlainConfiguration, Rules,
};
use biome_fs::BiomePath;
use biome_html_parser::{HtmlParserOptions, parse_html};
use biome_html_syntax::{HtmlFileSource, HtmlLanguage};
use biome_js_syntax::JsLanguage;
use camino::{Utf8Path, Utf8PathBuf};
use rustc_hash::FxHashSet;
Expand Down Expand Up @@ -236,6 +239,50 @@ fn override_inherits_global_formatter_when_not_specified() {
);
}

#[test]
fn html_parser_vue_only_applies_to_html_files() {
let configuration = Configuration {
html: Some(HtmlConfiguration {
parser: Some(HtmlParserConfiguration {
vue: Some(true.into()),
..HtmlParserConfiguration::default()
}),
..HtmlConfiguration::default()
}),
..Default::default()
};

let mut settings = Settings::default();
settings
.merge_with_configuration(configuration, None, vec![])
.expect("valid configuration");

let language = HtmlLanguage::lookup_settings(&settings.languages);

let html_options = resolve_html_parse_options(
&settings,
&language.parser,
Utf8Path::new("file.html"),
HtmlFileSource::html(),
);
let astro_options = resolve_html_parse_options(
&settings,
&language.parser,
Utf8Path::new("file.astro"),
HtmlFileSource::astro(),
);
let svelte_options = resolve_html_parse_options(
&settings,
&language.parser,
Utf8Path::new("file.svelte"),
HtmlFileSource::svelte(),
);

assert_no_parse_diagnostics("<div v-if=\"show\"></div>", html_options);
assert_has_parse_diagnostics("<div v-if=\"show\"></div>", astro_options);
assert_has_parse_diagnostics("<div v-if=\"show\"></div>", svelte_options);
}

#[test]
fn test_module_graph_resolution_kind_from_scan_kind() {
// Test all ScanKind variants map to correct ModuleGraphResolutionKind
Expand Down Expand Up @@ -295,3 +342,34 @@ fn test_project_scan_disables_module_graph_type_inference() {
"Project scan should NOT enable type inference"
);
}

fn resolve_html_parse_options(
settings: &Settings,
parser: &<HtmlLanguage as ServiceLanguage>::ParserSettings,
path: &Utf8Path,
file_source: HtmlFileSource,
) -> HtmlParserOptions {
HtmlLanguage::resolve_parse_options(
&settings.override_settings,
parser,
&BiomePath::new(Utf8PathBuf::from(path)),
&DocumentFileSource::from(file_source),
)
}

fn assert_has_parse_diagnostics(source: &str, options: HtmlParserOptions) {
let parse = parse_html(source, options);
assert!(
!parse.diagnostics().is_empty(),
"expected parsing to fail, but it succeeded"
);
}

fn assert_no_parse_diagnostics(source: &str, options: HtmlParserOptions) {
let parse = parse_html(source, options);
assert!(
parse.diagnostics().is_empty(),
"expected parsing to succeed, got diagnostics: {:?}",
parse.diagnostics()
);
}
8 changes: 8 additions & 0 deletions packages/@biomejs/backend-jsonrpc/src/workspace.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading