diff --git a/.changeset/eight-bars-unite.md b/.changeset/eight-bars-unite.md new file mode 100644 index 000000000000..97b725798f06 --- /dev/null +++ b/.changeset/eight-bars-unite.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +Added the rule [`useValidLang`](https://biomejs.dev/linter/rules/use-valid-lang) to the HTML language. diff --git a/crates/biome_css_formatter/tests/specs/css/global_suppression.css.snap b/crates/biome_css_formatter/tests/specs/css/global_suppression.css.snap index e38bbc8f336b..cd9ea5eb47fa 100644 --- a/crates/biome_css_formatter/tests/specs/css/global_suppression.css.snap +++ b/crates/biome_css_formatter/tests/specs/css/global_suppression.css.snap @@ -31,6 +31,7 @@ Indent width: 2 Line ending: LF Line width: 80 Quote style: Double Quotes +Trailing newline: true ----- ```css @@ -43,4 +44,5 @@ Quote style: Double Quotes } #exampleInputEmail1 { color: red; -}``` +} +``` diff --git a/crates/biome_graphql_formatter/tests/specs/graphql/suppression.graphql.snap b/crates/biome_graphql_formatter/tests/specs/graphql/suppression.graphql.snap index f77935826b4d..119063e52a70 100644 --- a/crates/biome_graphql_formatter/tests/specs/graphql/suppression.graphql.snap +++ b/crates/biome_graphql_formatter/tests/specs/graphql/suppression.graphql.snap @@ -42,6 +42,7 @@ Line ending: LF Line width: 80 Bracket spacing: true Quote style: Double Quotes +Trailing newline: true ----- ```graphql @@ -64,4 +65,5 @@ Quote style: Double Quotes reason: "Deprecated") @addExternalFields(source: "profiles") -}``` +} +``` diff --git a/crates/biome_grit_formatter/tests/specs/grit/global_suppression.grit.snap b/crates/biome_grit_formatter/tests/specs/grit/global_suppression.grit.snap index e79d1fb8ad56..3aa3f8f3772b 100644 --- a/crates/biome_grit_formatter/tests/specs/grit/global_suppression.grit.snap +++ b/crates/biome_grit_formatter/tests/specs/grit/global_suppression.grit.snap @@ -30,6 +30,7 @@ Indent width: 2 Line ending: LF Line width: 80 Attribute Position: Auto +Trailing newline: true ----- ```grit @@ -40,4 +41,5 @@ Attribute Position: Auto $names => $names[-1] } -}``` +} +``` diff --git a/crates/biome_html_analyze/src/lint/a11y.rs b/crates/biome_html_analyze/src/lint/a11y.rs index 73946eb0f7e4..6a1e3562957f 100644 --- a/crates/biome_html_analyze/src/lint/a11y.rs +++ b/crates/biome_html_analyze/src/lint/a11y.rs @@ -16,4 +16,5 @@ pub mod use_button_type; pub mod use_html_lang; pub mod use_iframe_title; pub mod use_valid_aria_role; -declare_lint_group! { pub A11y { name : "a11y" , rules : [self :: no_access_key :: NoAccessKey , self :: no_autofocus :: NoAutofocus , self :: no_distracting_elements :: NoDistractingElements , self :: no_header_scope :: NoHeaderScope , self :: no_positive_tabindex :: NoPositiveTabindex , self :: no_redundant_alt :: NoRedundantAlt , self :: no_svg_without_title :: NoSvgWithoutTitle , self :: use_alt_text :: UseAltText , self :: use_aria_props_for_role :: UseAriaPropsForRole , self :: use_button_type :: UseButtonType , self :: use_html_lang :: UseHtmlLang , self :: use_iframe_title :: UseIframeTitle , self :: use_valid_aria_role :: UseValidAriaRole ,] } } +pub mod use_valid_lang; +declare_lint_group! { pub A11y { name : "a11y" , rules : [self :: no_access_key :: NoAccessKey , self :: no_autofocus :: NoAutofocus , self :: no_distracting_elements :: NoDistractingElements , self :: no_header_scope :: NoHeaderScope , self :: no_positive_tabindex :: NoPositiveTabindex , self :: no_redundant_alt :: NoRedundantAlt , self :: no_svg_without_title :: NoSvgWithoutTitle , self :: use_alt_text :: UseAltText , self :: use_aria_props_for_role :: UseAriaPropsForRole , self :: use_button_type :: UseButtonType , self :: use_html_lang :: UseHtmlLang , self :: use_iframe_title :: UseIframeTitle , self :: use_valid_aria_role :: UseValidAriaRole , self :: use_valid_lang :: UseValidLang ,] } } diff --git a/crates/biome_html_analyze/src/lint/a11y/use_valid_lang.rs b/crates/biome_html_analyze/src/lint/a11y/use_valid_lang.rs new file mode 100644 index 000000000000..354dc49d14e5 --- /dev/null +++ b/crates/biome_html_analyze/src/lint/a11y/use_valid_lang.rs @@ -0,0 +1,195 @@ +use biome_analyze::context::RuleContext; +use biome_analyze::{Ast, Rule, RuleDiagnostic, RuleSource, declare_lint_rule}; +use biome_aria_metadata::{is_valid_country, is_valid_language, is_valid_script}; +use biome_console::markup; +use biome_diagnostics::Severity; +use biome_html_syntax::HtmlFileSource; +use biome_html_syntax::element_ext::AnyHtmlTagElement; +use biome_rowan::{AstNode, TextRange}; +use biome_rule_options::use_valid_lang::UseValidLangOptions; + +declare_lint_rule! { + /// Ensure that the attribute passed to the `lang` attribute is a correct ISO language and/or country. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```html,expect_diagnostic + /// + /// ``` + /// + /// ```html,expect_diagnostic + /// + /// ``` + /// + /// ```html,expect_diagnostic + /// + /// ``` + /// + /// ### Valid + /// + /// ```html + /// + /// ``` + pub UseValidLang { + version: "next", + name: "useValidLang", + language: "html", + sources: &[RuleSource::EslintJsxA11y("lang").same()], + recommended: true, + severity: Severity::Error, + } +} + +enum InvalidKind { + Language, + Country, + Script, + Value, +} + +pub struct UseValidLangState { + invalid_kind: InvalidKind, + attribute_range: TextRange, +} + +impl Rule for UseValidLang { + type Query = Ast; + type State = UseValidLangState; + type Signals = Option; + type Options = UseValidLangOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + let element_text = node.name().ok()?.value_token().ok()?; + let source_type = ctx.source_type::(); + let matches_tag = if source_type.is_html() { + element_text.text_trimmed().eq_ignore_ascii_case("html") + } else { + element_text.text_trimmed() == "html" + }; + if !matches_tag { + return None; + } + + let attribute = node.find_attribute_by_name("lang")?; + let attribute_value = attribute.initializer()?.value().ok()?; + let attribute_static_value = attribute_value.as_static_value()?; + let attribute_text = attribute_static_value.text(); + let mut split_value = attribute_text.split('-'); + match (split_value.next(), split_value.next(), split_value.next()) { + (Some(language), Some(script), Some(country)) => { + if split_value.next().is_some() { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Value, + }); + } else if !is_valid_language(language) { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Language, + }); + } else if !is_valid_script(script) { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Script, + }); + } else if !is_valid_country(country) { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Country, + }); + } + } + + (Some(language), Some(script_or_country), None) => { + if !is_valid_language(language) { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Language, + }); + } else if !is_valid_script(script_or_country) + && !is_valid_country(script_or_country) + { + match script_or_country.len() { + 4 => { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Script, + }); + } + 2 | 3 => { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Country, + }); + } + _ => { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Value, + }); + } + } + } + } + + (Some(language), None, None) => { + if !is_valid_language(language) { + return Some(UseValidLangState { + attribute_range: attribute_value.range(), + invalid_kind: InvalidKind::Language, + }); + } + } + _ => {} + } + + None + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + let mut diagnostic = RuleDiagnostic::new( + rule_category!(), + state.attribute_range, + markup! { + "Provide a valid value for the ""lang"" attribute." + }, + ); + diagnostic = match state.invalid_kind { + InvalidKind::Language => { + let languages = biome_aria_metadata::languages(); + let languages = if languages.len() > 15 { + &languages[..15] + } else { + languages + }; + + diagnostic.footer_list("Some of valid languages:", languages) + } + InvalidKind::Country => { + let countries = biome_aria_metadata::countries(); + let countries = if countries.len() > 15 { + &countries[..15] + } else { + countries + }; + + diagnostic.footer_list("Some of valid countries:", countries) + } + InvalidKind::Script => { + let scripts = biome_aria_metadata::scripts(); + let scripts = if scripts.len() > 15 { + &scripts[..15] + } else { + scripts + }; + + diagnostic.footer_list("Some of valid scripts:", scripts) + } + InvalidKind::Value => diagnostic, + }; + Some(diagnostic) + } +} diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.html b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.html new file mode 100644 index 000000000000..b3a9e14a0c4a --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.html.snap b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.html.snap new file mode 100644 index 000000000000..1f38e6a4d55d --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.html.snap @@ -0,0 +1,176 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.html +--- +# Input +```html + + + + + + + + +``` + +# Diagnostics +``` +invalid.html:2:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 1 │ + > 2 │ + │ ^^^^^^^ + 3 │ + 4 │ + + i Some of valid languages: + + - ab + - aa + - af + - sq + - am + - ar + - an + - hy + - as + - ay + - az + - ba + - eu + - bn + - dz + + +``` + +``` +invalid.html:3:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 1 │ + 2 │ + > 3 │ + │ ^^^^^^^^^^ + 4 │ + 5 │ + + +``` + +``` +invalid.html:4:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 2 │ + 3 │ + > 4 │ + │ ^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ + + i Some of valid scripts: + + - Arab + - Armn + - Beng + - Cyrl + - Deva + - Ethi + - Grek + - Gujr + - Guru + - Hang + - Hani + - Hans + - Hant + - Hebr + - Hira + + +``` + +``` +invalid.html:5:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 3 │ + 4 │ + > 5 │ + │ ^^^^^^^^^ + 6 │ + 7 │ + + i Some of valid scripts: + + - Arab + - Armn + - Beng + - Cyrl + - Deva + - Ethi + - Grek + - Gujr + - Guru + - Hang + - Hani + - Hans + - Hant + - Hebr + - Hira + + +``` + +``` +invalid.html:6:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 4 │ + 5 │ + > 6 │ + │ ^^^^^^^^^^^^ + 7 │ + 8 │ + + i Some of valid countries: + + - AF + - AL + - DZ + - AS + - AD + - AO + - AI + - AQ + - AG + - AR + - AM + - AW + - AU + - AT + - AZ + + +``` + +``` +invalid.html:7:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 5 │ + 6 │ + > 7 │ + │ ^^^^^^^^^^^^^^^^ + 8 │ + + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.vue b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.vue new file mode 100644 index 000000000000..b3a9e14a0c4a --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.vue @@ -0,0 +1,7 @@ + + + + + + + diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.vue.snap b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.vue.snap new file mode 100644 index 000000000000..16df2529085d --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/invalid.vue.snap @@ -0,0 +1,176 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.vue +--- +# Input +```html + + + + + + + + +``` + +# Diagnostics +``` +invalid.vue:2:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 1 │ + > 2 │ + │ ^^^^^^^ + 3 │ + 4 │ + + i Some of valid languages: + + - ab + - aa + - af + - sq + - am + - ar + - an + - hy + - as + - ay + - az + - ba + - eu + - bn + - dz + + +``` + +``` +invalid.vue:3:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 1 │ + 2 │ + > 3 │ + │ ^^^^^^^^^^ + 4 │ + 5 │ + + +``` + +``` +invalid.vue:4:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 2 │ + 3 │ + > 4 │ + │ ^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ + + i Some of valid scripts: + + - Arab + - Armn + - Beng + - Cyrl + - Deva + - Ethi + - Grek + - Gujr + - Guru + - Hang + - Hani + - Hans + - Hant + - Hebr + - Hira + + +``` + +``` +invalid.vue:5:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 3 │ + 4 │ + > 5 │ + │ ^^^^^^^^^ + 6 │ + 7 │ + + i Some of valid scripts: + + - Arab + - Armn + - Beng + - Cyrl + - Deva + - Ethi + - Grek + - Gujr + - Guru + - Hang + - Hani + - Hans + - Hant + - Hebr + - Hira + + +``` + +``` +invalid.vue:6:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 4 │ + 5 │ + > 6 │ + │ ^^^^^^^^^^^^ + 7 │ + 8 │ + + i Some of valid countries: + + - AF + - AL + - DZ + - AS + - AD + - AO + - AI + - AQ + - AG + - AR + - AM + - AW + - AU + - AT + - AZ + + +``` + +``` +invalid.vue:7:12 lint/a11y/useValidLang ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Provide a valid value for the lang attribute. + + 5 │ + 6 │ + > 7 │ + │ ^^^^^^^^^^^^^^^^ + 8 │ + + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.html b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.html new file mode 100644 index 000000000000..a8888cfe790e --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.html @@ -0,0 +1,7 @@ + +; +; +; +; +; +; diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.html.snap b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.html.snap new file mode 100644 index 000000000000..6c1a5b1b3b16 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.html.snap @@ -0,0 +1,15 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.html +--- +# Input +```html + +; +; +; +; +; +; + +``` diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.vue b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.vue new file mode 100644 index 000000000000..2bf4563f8394 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.vue @@ -0,0 +1,8 @@ + + +; +; +; +; +; +; diff --git a/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.vue.snap b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.vue.snap new file mode 100644 index 000000000000..b027cd8a9634 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/a11y/useValidLang/valid.vue.snap @@ -0,0 +1,16 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.vue +--- +# Input +```html + + +; +; +; +; +; +; + +``` diff --git a/crates/biome_html_factory/src/make.rs b/crates/biome_html_factory/src/make.rs index ef5007be66b0..ccda36eff599 100644 --- a/crates/biome_html_factory/src/make.rs +++ b/crates/biome_html_factory/src/make.rs @@ -15,3 +15,12 @@ pub fn html_string_literal(text: &str) -> HtmlSyntaxToken { [], ) } + +/// Create a new token with the specified syntax kind and no attached trivia +pub fn token(kind: HtmlSyntaxKind) -> HtmlSyntaxToken { + if let Some(text) = kind.to_string() { + HtmlSyntaxToken::new_detached(kind, text, [], []) + } else { + panic!("token kind {kind:?} cannot be transformed to text") + } +} diff --git a/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap b/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap index 2b4005916498..c0f12abdf8b2 100644 --- a/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap +++ b/crates/biome_html_formatter/tests/specs/html/suppressions/global_suppression.html.snap @@ -37,6 +37,7 @@ Bracket same line: false Whitespace sensitivity: css Indent script and style: false Self close void elements: never +Trailing newline: true ----- ```html @@ -51,4 +52,5 @@ Self close void elements: never - ``` + +``` diff --git a/crates/biome_html_syntax/src/attribute_ext.rs b/crates/biome_html_syntax/src/attribute_ext.rs new file mode 100644 index 000000000000..78e38d854c5c --- /dev/null +++ b/crates/biome_html_syntax/src/attribute_ext.rs @@ -0,0 +1,11 @@ +use crate::AnyHtmlAttributeInitializer; +use crate::static_value::StaticValue; + +impl AnyHtmlAttributeInitializer { + pub fn as_static_value(&self) -> Option { + match self { + Self::HtmlSingleTextExpression(_) => None, + Self::HtmlString(value) => Some(StaticValue::String(value.value_token().ok()?)), + } + } +} diff --git a/crates/biome_html_syntax/src/lib.rs b/crates/biome_html_syntax/src/lib.rs index 0a9717abb2e4..10972d592e8a 100644 --- a/crates/biome_html_syntax/src/lib.rs +++ b/crates/biome_html_syntax/src/lib.rs @@ -2,10 +2,12 @@ #[macro_use] mod attr_ext; +pub mod attribute_ext; pub mod element_ext; mod file_source; mod generated; mod script_type; +pub mod static_value; mod string_ext; mod syntax_node; diff --git a/crates/biome_html_syntax/src/static_value.rs b/crates/biome_html_syntax/src/static_value.rs new file mode 100644 index 000000000000..a498c536b763 --- /dev/null +++ b/crates/biome_html_syntax/src/static_value.rs @@ -0,0 +1,143 @@ +use biome_rowan::TextRange; + +use crate::{HtmlSyntaxKind, HtmlSyntaxToken}; + +#[derive(Debug, Clone, Eq, PartialEq)] +/// static values defined in JavaScript's expressions +pub enum StaticValue { + Boolean(HtmlSyntaxToken), + Null(HtmlSyntaxToken), + Undefined(HtmlSyntaxToken), + Number(HtmlSyntaxToken), + BigInt(HtmlSyntaxToken), + // The string can be empty. + String(HtmlSyntaxToken), + /// This is used to represent the empty template string. + EmptyString(TextRange), +} + +impl StaticValue { + /// Return `true` if the value is falsy + /// + /// ## Examples + /// + /// ``` + /// use biome_html_syntax::{T, static_value::StaticValue}; + /// use biome_html_factory::make; + /// + /// let bool = make::token(T![false]); + /// assert!(StaticValue::Boolean(bool).is_falsy()); + /// ``` + pub fn is_falsy(&self) -> bool { + match self { + Self::Boolean(token) => token.text_trimmed() == "false", + Self::Null(_) | Self::Undefined(_) | Self::EmptyString(_) => true, + Self::Number(token) => token.text_trimmed() == "0", + Self::BigInt(token) => token.text_trimmed() == "0n", + Self::String(_) => self.text().is_empty(), + } + } + + /// Return a string of the static value + /// + /// ## Examples + /// + /// ``` + /// use biome_html_syntax::{T, static_value::StaticValue}; + /// use biome_html_factory::make; + /// + /// let bool = make::token(T![false]); + /// assert_eq!(StaticValue::Boolean(bool).text(), "false"); + /// ``` + pub fn text(&self) -> &str { + match self { + Self::Boolean(token) + | Self::Null(token) + | Self::Undefined(token) + | Self::Number(token) + | Self::BigInt(token) => token.text_trimmed(), + Self::String(token) => { + let text = token.text_trimmed(); + if matches!(token.kind(), HtmlSyntaxKind::HTML_STRING_LITERAL) { + // SAFETY: string literal token have a delimiters at the start and the end of the string + return &text[1..text.len() - 1]; + } + text + } + Self::EmptyString(_) => "", + } + } + + /// Return teh range of the static value. + /// + /// ## Examples + /// + /// ``` + /// use biome_html_syntax::{T, static_value::StaticValue}; + /// use biome_html_factory::make; + /// + /// let bool = make::token(T![false]); + /// assert_eq!(StaticValue::Boolean(bool.clone()).range(), bool.text_trimmed_range()); + /// ``` + pub fn range(&self) -> TextRange { + match self { + Self::Boolean(token) + | Self::Null(token) + | Self::Undefined(token) + | Self::Number(token) + | Self::BigInt(token) + | Self::String(token) => token.text_trimmed_range(), + Self::EmptyString(range) => *range, + } + } + + /// Return `true` if the static value doesn't match the given string value and it is + /// 1. A string literal + /// 2. A template literal with no substitutions + /// + /// ## Examples + /// + /// ``` + /// use biome_html_syntax::static_value::StaticValue; + /// use biome_html_factory::make; + /// use biome_rowan::TriviaPieceKind; + /// + /// let str_literal = make::html_string_literal("foo") + /// .with_leading_trivia(vec![(TriviaPieceKind::Whitespace, " ")]); + /// assert!(StaticValue::String(str_literal).is_not_string_constant("bar")); + /// ``` + pub fn is_not_string_constant(&self, text: &str) -> bool { + match self { + Self::String(_) | Self::EmptyString(_) => self.text() != text, + _ => false, + } + } + + /// Return a string if the static value is + /// 1. A string literal + /// 2. A template literal with no substitutions + /// + /// ## Examples + /// + /// ``` + /// use biome_html_syntax::static_value::StaticValue; + /// use biome_html_factory::make; + /// use biome_rowan::TriviaPieceKind; + /// + /// let str_literal = make::html_string_literal("foo") + /// .with_leading_trivia(vec![(TriviaPieceKind::Whitespace, " ")]); + /// assert_eq!(StaticValue::String(str_literal).as_string_constant().unwrap(), "foo"); + /// ``` + pub fn as_string_constant(&self) -> Option<&str> { + match self { + Self::String(_) | Self::EmptyString(_) => Some(self.text()), + _ => None, + } + } +} + +impl AsRef for StaticValue { + fn as_ref(&self) -> &str { + self.text() + } +} diff --git a/crates/biome_js_formatter/tests/specs/js/module/global_suppression.js.snap b/crates/biome_js_formatter/tests/specs/js/module/global_suppression.js.snap index b8faf260b4b5..50bec93f166b 100644 --- a/crates/biome_js_formatter/tests/specs/js/module/global_suppression.js.snap +++ b/crates/biome_js_formatter/tests/specs/js/module/global_suppression.js.snap @@ -36,6 +36,7 @@ Bracket same line: false Attribute Position: Auto Expand lists: Auto Operator linebreak: After +Trailing newline: true ----- ```js @@ -43,4 +44,5 @@ Operator linebreak: After let a = 1 ; -function name() { return " " };``` +function name() { return " " }; +``` diff --git a/crates/biome_json_formatter/tests/specs/json/global_suppression.jsonc.snap b/crates/biome_json_formatter/tests/specs/json/global_suppression.jsonc.snap index ee8a988cee7c..a9258076e1d0 100644 --- a/crates/biome_json_formatter/tests/specs/json/global_suppression.jsonc.snap +++ b/crates/biome_json_formatter/tests/specs/json/global_suppression.jsonc.snap @@ -31,6 +31,7 @@ Line width: 80 Trailing commas: None Expand: Auto Bracket spacing: true +Trailing newline: true ----- ```jsonc @@ -40,4 +41,5 @@ Bracket spacing: true "a": "b", "c": "d", "e": "f" -}``` +} +```