diff --git a/.changeset/strong-mirrors-attend.md b/.changeset/strong-mirrors-attend.md new file mode 100644 index 000000000000..6c29c59d5d7d --- /dev/null +++ b/.changeset/strong-mirrors-attend.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Added the nursery rule [`noInlineStyles`](https://biomejs.dev/linter/rules/no-inline-styles/). The rule disallows the use of inline `style` attributes in HTML and the `style` prop in JSX, including `React.createElement` calls. Inline styles make code harder to maintain and can interfere with Content Security Policy. diff --git a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs index 19098c2ea68f..28d669848317 100644 --- a/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs +++ b/crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs @@ -241,6 +241,18 @@ pub(crate) fn migrate_eslint_any_rule( .get_or_insert(Default::default()); rule.set_level(rule.level().max(rule_severity.into())); } + "@html-eslint/no-inline-styles" => { + if !options.include_nursery { + results.add(eslint_name, eslint_to_biome::RuleMigrationResult::Nursery); + return false; + } + let group = rules.nursery.get_or_insert_with(Default::default); + let rule = group + .unwrap_group_as_mut() + .no_inline_styles + .get_or_insert(Default::default()); + rule.set_level(rule.level().max(rule_severity.into())); + } "@html-eslint/no-positive-tabindex" => { let group = rules.a11y.get_or_insert_with(Default::default); let rule = group diff --git a/crates/biome_configuration/src/analyzer/linter/rules.rs b/crates/biome_configuration/src/analyzer/linter/rules.rs index beb53b03e6cb..74d261179bdd 100644 --- a/crates/biome_configuration/src/analyzer/linter/rules.rs +++ b/crates/biome_configuration/src/analyzer/linter/rules.rs @@ -217,6 +217,7 @@ pub enum RuleName { NoImportantStyles, NoIncrementDecrement, NoInferrableTypes, + NoInlineStyles, NoInnerDeclarations, NoInteractiveElementToNoninteractiveRole, NoInvalidBuiltinInstantiation, @@ -684,6 +685,7 @@ impl RuleName { Self::NoImportantStyles => "noImportantStyles", Self::NoIncrementDecrement => "noIncrementDecrement", Self::NoInferrableTypes => "noInferrableTypes", + Self::NoInlineStyles => "noInlineStyles", Self::NoInnerDeclarations => "noInnerDeclarations", Self::NoInteractiveElementToNoninteractiveRole => { "noInteractiveElementToNoninteractiveRole" @@ -1155,6 +1157,7 @@ impl RuleName { Self::NoImportantStyles => RuleGroup::Complexity, Self::NoIncrementDecrement => RuleGroup::Nursery, Self::NoInferrableTypes => RuleGroup::Style, + Self::NoInlineStyles => RuleGroup::Nursery, Self::NoInnerDeclarations => RuleGroup::Correctness, Self::NoInteractiveElementToNoninteractiveRole => RuleGroup::A11y, Self::NoInvalidBuiltinInstantiation => RuleGroup::Correctness, @@ -1627,6 +1630,7 @@ impl std::str::FromStr for RuleName { "noImportantStyles" => Ok(Self::NoImportantStyles), "noIncrementDecrement" => Ok(Self::NoIncrementDecrement), "noInferrableTypes" => Ok(Self::NoInferrableTypes), + "noInlineStyles" => Ok(Self::NoInlineStyles), "noInnerDeclarations" => Ok(Self::NoInnerDeclarations), "noInteractiveElementToNoninteractiveRole" => { Ok(Self::NoInteractiveElementToNoninteractiveRole) diff --git a/crates/biome_configuration/src/generated/linter_options_check.rs b/crates/biome_configuration/src/generated/linter_options_check.rs index 53eaafb7ff92..33b0ac669c79 100644 --- a/crates/biome_configuration/src/generated/linter_options_check.rs +++ b/crates/biome_configuration/src/generated/linter_options_check.rs @@ -630,6 +630,11 @@ pub fn config_side_rule_options_types() -> Vec<(&'static str, &'static str, Type "noInferrableTypes", TypeId::of::(), )); + result.push(( + "nursery", + "noInlineStyles", + TypeId::of::(), + )); result.push(( "correctness", "noInnerDeclarations", diff --git a/crates/biome_diagnostics_categories/src/categories.rs b/crates/biome_diagnostics_categories/src/categories.rs index fde5e5c76e7e..34ecfd19a7f0 100644 --- a/crates/biome_diagnostics_categories/src/categories.rs +++ b/crates/biome_diagnostics_categories/src/categories.rs @@ -199,6 +199,7 @@ define_categories! { "lint/nursery/noHexColors": "https://biomejs.dev/linter/rules/no-hex-colors", "lint/nursery/noImplicitCoercion": "https://biomejs.dev/linter/rules/no-implicit-coercion", "lint/nursery/noIncrementDecrement": "https://biomejs.dev/linter/rules/no-increment-decrement", + "lint/nursery/noInlineStyles": "https://biomejs.dev/linter/rules/no-inline-styles", "lint/nursery/noJsxPropsBind": "https://biomejs.dev/linter/rules/no-jsx-props-bind", "lint/nursery/noLeakedRender": "https://biomejs.dev/linter/rules/no-leaked-render", "lint/nursery/noMissingGenericFamilyKeyword": "https://biomejs.dev/linter/rules/no-missing-generic-family-keyword", @@ -227,6 +228,7 @@ define_categories! { "lint/nursery/noShadow": "https://biomejs.dev/linter/rules/no-shadow", "lint/nursery/noSyncScripts": "https://biomejs.dev/linter/rules/no-sync-scripts", "lint/nursery/noTernary": "https://biomejs.dev/linter/rules/no-ternary", + "lint/nursery/noTopLevelLiterals": "https://biomejs.dev/linter/rules/no-top-level-literals", "lint/nursery/noUndeclaredEnvVars": "https://biomejs.dev/linter/rules/no-undeclared-env-vars", "lint/nursery/noUnknownAttribute": "https://biomejs.dev/linter/rules/no-unknown-attribute", "lint/nursery/noUnnecessaryConditions": "https://biomejs.dev/linter/rules/no-unnecessary-conditions", @@ -272,7 +274,6 @@ define_categories! { "lint/nursery/useScopedStyles": "https://biomejs.dev/linter/rules/use-scoped-styles", "lint/nursery/useSortedClasses": "https://biomejs.dev/linter/rules/use-sorted-classes", "lint/nursery/useSpread": "https://biomejs.dev/linter/rules/use-spread", - "lint/nursery/noTopLevelLiterals": "https://biomejs.dev/linter/rules/no-top-level-literals", "lint/nursery/useUnicodeRegex": "https://biomejs.dev/linter/rules/use-unicode-regex", "lint/nursery/useUniqueArgumentNames": "https://biomejs.dev/linter/rules/use-unique-argument-names", "lint/nursery/useUniqueFieldDefinitionNames": "https://biomejs.dev/linter/rules/use-unique-field-definition-names", diff --git a/crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs b/crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs new file mode 100644 index 000000000000..a291eeea0fa6 --- /dev/null +++ b/crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs @@ -0,0 +1,154 @@ +use biome_analyze::{ + Ast, FixKind, Rule, RuleDiagnostic, RuleSource, context::RuleContext, declare_lint_rule, +}; +use biome_console::markup; +use biome_html_syntax::{AnyHtmlElement, HtmlAttribute, HtmlAttributeList, HtmlFileSource}; +use biome_rowan::{AstNode, BatchMutationExt, TextRange, TokenText}; +use biome_rule_options::no_inline_styles::NoInlineStylesOptions; +use biome_string_case::StrOnlyExtension; + +use crate::HtmlRuleAction; + +declare_lint_rule! { + /// Disallow the use of inline styles. + /// + /// Inline styles via the `style` attribute make code harder to maintain and override, + /// prevent reusability of styling, and can be a security concern when implementing + /// a strict Content Security Policy (CSP). + /// + /// Instead of inline styles, use CSS classes, CSS modules, or a styling library. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```html,expect_diagnostic + ///
+ /// ``` + /// + /// ```html,expect_diagnostic + ///

Hello

+ /// ``` + /// + /// ### Valid + /// + /// ```html + ///
+ /// ``` + /// + /// ```html + ///

Hello

+ /// ``` + /// + /// ## Resources + /// + /// - [Content Security Policy: Allowing inline styles](https://content-security-policy.com/examples/allow-inline-style) + /// + pub NoInlineStyles { + version: "next", + name: "noInlineStyles", + language: "html", + recommended: false, + sources: &[ + RuleSource::HtmlEslint("no-inline-styles").same(), + ], + fix_kind: FixKind::Unsafe, + } +} + +impl Rule for NoInlineStyles { + type Query = Ast; + type State = TextRange; + type Signals = Option; + type Options = NoInlineStylesOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + match node { + AnyHtmlElement::HtmlElement(element) => { + let opening = element.opening_element().ok()?; + + if let Some(name) = opening.tag_name() + && is_custom_component(name, ctx) + { + return None; + } + + find_style_attribute(opening.attributes()).map(|attribute| attribute.range()) + } + AnyHtmlElement::HtmlSelfClosingElement(self_closing_element) => { + if let Some(name) = self_closing_element.tag_name() + && is_custom_component(name, ctx) + { + return None; + } + + find_style_attribute(self_closing_element.attributes()) + .map(|attribute| attribute.range()) + } + _ => None, + } + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + Some( + RuleDiagnostic::new( + rule_category!(), + state, + markup! { + "Unexpected ""style"" attribute." + }, + ) + .note(markup! { + "Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead." + }), + ) + } + + fn action(ctx: &RuleContext, _state: &Self::State) -> Option { + let node = ctx.query(); + let mut mutation = ctx.root().begin(); + + match node { + AnyHtmlElement::HtmlElement(element) => { + let opening = element.opening_element().ok()?; + mutation.remove_node(find_style_attribute(opening.attributes()).unwrap()) + } + AnyHtmlElement::HtmlSelfClosingElement(self_closing_element) => mutation + .remove_node(find_style_attribute(self_closing_element.attributes()).unwrap()), + _ => {} + } + + Some(HtmlRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + ctx.metadata().applicability(), + markup! { "Remove the ""style"" attribute." }.to_owned(), + mutation, + )) + } +} + +fn find_style_attribute(attributes: HtmlAttributeList) -> Option { + for attribute in attributes { + if let Some(attribute) = attribute.as_html_attribute() + && let Some(name) = attribute.name().ok() + && let Some(value_token) = name.value_token().ok() + && value_token.text_trimmed().to_lowercase_cow() == "style" + { + return Some(attribute.clone()); + } + } + + None +} + +fn is_custom_component(element_name: TokenText, ctx: &RuleContext) -> bool { + let source_type = ctx.source_type::(); + + if source_type.is_html() { + return false; + } + + element_name.text() != element_name.to_lowercase_cow() +} diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/invalid.astro b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/invalid.astro new file mode 100644 index 000000000000..6df25703bf7c --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/invalid.astro @@ -0,0 +1,11 @@ + + +
+ +

Hello

+ +Hidden + + + +Link diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/invalid.astro.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/invalid.astro.snap new file mode 100644 index 000000000000..a654619614b7 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/invalid.astro.snap @@ -0,0 +1,134 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.astro +--- +# Input +```html + + +
+ +

Hello

+ +Hidden + + + +Link + +``` + +# Diagnostics +``` +invalid.astro:3:6 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 1 │ + 2 │ + > 3 │
+ │ ^^^^^^^^^^^^^^^^^^^ + 4 │ + 5 │

Hello

+ + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 3 │ + │ ------------------- + +``` + +``` +invalid.astro:5:4 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 3 │
+ 4 │ + > 5 │

Hello

+ │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ + 7 │ Hidden + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 5 │ Hello

+ │ ------------------------ + +``` + +``` +invalid.astro:7:7 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 5 │

Hello

+ 6 │ + > 7 │ Hidden + │ ^^^^^^^^^^^^^^^^^^^^^^ + 8 │ + 9 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 7 │ Hidden + │ ---------------------- + +``` + +``` +invalid.astro:9:20 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 7 │ Hidden + 8 │ + > 9 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ + 11 │ Link + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 9 │ + │ --------------------------------- + +``` + +``` +invalid.astro:11:13 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 9 │ + 10 │ + > 11 │ Link + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 11 │ Link + │ ------------------------------ + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/valid.astro b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/valid.astro new file mode 100644 index 000000000000..7338de373eac --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/valid.astro @@ -0,0 +1,15 @@ + + +
+ +

Hello

+ +Hidden + + + +Link + +Not inline style + + diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/valid.astro.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/valid.astro.snap new file mode 100644 index 000000000000..69b09e4848e0 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/astro/valid.astro.snap @@ -0,0 +1,23 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.astro +--- +# Input +```html + + +
+ +

Hello

+ +Hidden + + + +Link + +Not inline style + + + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/invalid.html b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/invalid.html new file mode 100644 index 000000000000..6df25703bf7c --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/invalid.html @@ -0,0 +1,11 @@ + + +
+ +

Hello

+ +Hidden + + + +Link diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/invalid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/invalid.html.snap new file mode 100644 index 000000000000..64526d27746e --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/invalid.html.snap @@ -0,0 +1,134 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.html +--- +# Input +```html + + +
+ +

Hello

+ +Hidden + + + +Link + +``` + +# Diagnostics +``` +invalid.html:3:6 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 1 │ + 2 │ + > 3 │
+ │ ^^^^^^^^^^^^^^^^^^^ + 4 │ + 5 │

Hello

+ + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 3 │ + │ ------------------- + +``` + +``` +invalid.html:5:4 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 3 │
+ 4 │ + > 5 │

Hello

+ │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ + 7 │ Hidden + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 5 │ Hello

+ │ ------------------------ + +``` + +``` +invalid.html:7:7 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 5 │

Hello

+ 6 │ + > 7 │ Hidden + │ ^^^^^^^^^^^^^^^^^^^^^^ + 8 │ + 9 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 7 │ Hidden + │ ---------------------- + +``` + +``` +invalid.html:9:20 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 7 │ Hidden + 8 │ + > 9 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ + 11 │ Link + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 9 │ + │ --------------------------------- + +``` + +``` +invalid.html:11:13 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 9 │ + 10 │ + > 11 │ Link + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 11 │ Link + │ ------------------------------ + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/invalid.svelte b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/invalid.svelte new file mode 100644 index 000000000000..6df25703bf7c --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/invalid.svelte @@ -0,0 +1,11 @@ + + +
+ +

Hello

+ +Hidden + + + +Link diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/invalid.svelte.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/invalid.svelte.snap new file mode 100644 index 000000000000..c83ab8fa7a7f --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/invalid.svelte.snap @@ -0,0 +1,134 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.svelte +--- +# Input +```html + + +
+ +

Hello

+ +Hidden + + + +Link + +``` + +# Diagnostics +``` +invalid.svelte:3:6 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 1 │ + 2 │ + > 3 │
+ │ ^^^^^^^^^^^^^^^^^^^ + 4 │ + 5 │

Hello

+ + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 3 │ + │ ------------------- + +``` + +``` +invalid.svelte:5:4 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 3 │
+ 4 │ + > 5 │

Hello

+ │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ + 7 │ Hidden + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 5 │ Hello

+ │ ------------------------ + +``` + +``` +invalid.svelte:7:7 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 5 │

Hello

+ 6 │ + > 7 │ Hidden + │ ^^^^^^^^^^^^^^^^^^^^^^ + 8 │ + 9 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 7 │ Hidden + │ ---------------------- + +``` + +``` +invalid.svelte:9:20 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 7 │ Hidden + 8 │ + > 9 │ + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 10 │ + 11 │ Link + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 9 │ + │ --------------------------------- + +``` + +``` +invalid.svelte:11:13 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 9 │ + 10 │ + > 11 │ Link + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 12 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 11 │ Link + │ ------------------------------ + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/valid.svelte b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/valid.svelte new file mode 100644 index 000000000000..7338de373eac --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/valid.svelte @@ -0,0 +1,15 @@ + + +
+ +

Hello

+ +Hidden + + + +Link + +Not inline style + + diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/valid.svelte.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/valid.svelte.snap new file mode 100644 index 000000000000..dd697ea07b5b --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/svelte/valid.svelte.snap @@ -0,0 +1,23 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.svelte +--- +# Input +```html + + +
+ +

Hello

+ +Hidden + + + +Link + +Not inline style + + + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/valid.html b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/valid.html new file mode 100644 index 000000000000..f324c034a0f5 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/valid.html @@ -0,0 +1,19 @@ + + +
+ +

Hello

+ +Hidden + + + +Link + + + +Not inline style diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/valid.html.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/valid.html.snap new file mode 100644 index 000000000000..482258a0075b --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/valid.html.snap @@ -0,0 +1,27 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.html +--- +# Input +```html + + +
+ +

Hello

+ +Hidden + + + +Link + + + +Not inline style + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/invalid.vue b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/invalid.vue new file mode 100644 index 000000000000..de1bfcf20a51 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/invalid.vue @@ -0,0 +1,13 @@ + + + diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/invalid.vue.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/invalid.vue.snap new file mode 100644 index 000000000000..c25cf96a9150 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/invalid.vue.snap @@ -0,0 +1,136 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: invalid.vue +--- +# Input +```html + + + + +``` + +# Diagnostics +``` +invalid.vue:4:7 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 3 │ + 14 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 12 │ → Link + │ ------------------------------ + +``` diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/valid.vue b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/valid.vue new file mode 100644 index 000000000000..a1d53e980688 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/valid.vue @@ -0,0 +1,17 @@ + + + diff --git a/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/valid.vue.snap b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/valid.vue.snap new file mode 100644 index 000000000000..3e7ced31b8d4 --- /dev/null +++ b/crates/biome_html_analyze/tests/specs/nursery/noInlineStyles/vue/valid.vue.snap @@ -0,0 +1,25 @@ +--- +source: crates/biome_html_analyze/tests/spec_tests.rs +expression: valid.vue +--- +# Input +```html + + + + +``` diff --git a/crates/biome_js_analyze/src/lint/nursery/no_inline_styles.rs b/crates/biome_js_analyze/src/lint/nursery/no_inline_styles.rs new file mode 100644 index 000000000000..5473dd71006b --- /dev/null +++ b/crates/biome_js_analyze/src/lint/nursery/no_inline_styles.rs @@ -0,0 +1,178 @@ +use crate::JsRuleAction; +use crate::react::ReactCreateElementCall; +use crate::services::semantic::Semantic; +use crate::utils::batch::JsBatchMutation; +use biome_analyze::context::RuleContext; +use biome_analyze::{FixKind, Rule, RuleDiagnostic, declare_lint_rule}; +use biome_console::markup; +use biome_js_semantic::SemanticModel; +use biome_js_syntax::jsx_ext::AnyJsxElement; +use biome_js_syntax::{ + AnyJsObjectMember, JsCallExpression, JsPropertyObjectMember, JsSyntaxToken, JsxAttribute, +}; +use biome_rowan::{AstNode, BatchMutationExt, TextRange, declare_node_union}; +use biome_rule_options::no_inline_styles::NoInlineStylesOptions; +use biome_string_case::StrOnlyExtension; + +declare_lint_rule! { + /// Disallow the use of inline styles. + /// + /// Inline styles via the `style` attribute make code harder to maintain and override, + /// prevent reusability of styling, and can be a security concern when implementing + /// a strict Content Security Policy (CSP). + /// + /// Instead of inline styles, use CSS classes, CSS modules, or a styling library. + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```jsx,expect_diagnostic + ///
Error
+ /// ``` + /// + /// ```js,expect_diagnostic + /// React.createElement("div", { style: { color: "red" } }); + /// ``` + /// + /// ### Valid + /// + /// ```jsx + ///
Error
+ /// ``` + /// + /// ```js + /// React.createElement("div", { className: "container" }); + /// ``` + /// + /// ## Resources + /// + /// - [Content Security Policy: Allowing inline styles](https://content-security-policy.com/examples/allow-inline-style) + /// + pub NoInlineStyles { + version: "next", + name: "noInlineStyles", + language: "js", + recommended: false, + fix_kind: FixKind::Unsafe, + } +} + +impl Rule for NoInlineStyles { + type Query = Semantic; + type State = TextRange; + type Signals = Option; + type Options = NoInlineStylesOptions; + + fn run(ctx: &RuleContext) -> Self::Signals { + let node = ctx.query(); + + match node { + NoInlineStylesQuery::AnyJsxElement(element) => { + let attribute = find_style_attribute(element)?; + Some(attribute.range()) + } + NoInlineStylesQuery::JsCallExpression(call_expression) => { + let model = ctx.model(); + let object_member = find_style_object_member(call_expression, model)?; + Some(object_member.range()) + } + } + } + + fn diagnostic(_ctx: &RuleContext, state: &Self::State) -> Option { + Some( + RuleDiagnostic::new( + rule_category!(), + state, + markup! { + "Unexpected ""style"" attribute." + }, + ) + .note(markup! { + "Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead." + }), + ) + } + + fn action(ctx: &RuleContext, _state: &Self::State) -> Option { + let node = ctx.query(); + let mut mutation = ctx.root().begin(); + match node { + NoInlineStylesQuery::AnyJsxElement(element) => { + let attribute = find_style_attribute(element)?; + mutation.remove_node(attribute); + } + NoInlineStylesQuery::JsCallExpression(call_expression) => { + let model = ctx.model(); + let object_member = find_style_object_member(call_expression, model)?; + mutation.remove_js_object_member(AnyJsObjectMember::JsPropertyObjectMember( + object_member, + )); + } + } + + Some(JsRuleAction::new( + ctx.metadata().action_category(ctx.category(), ctx.group()), + ctx.metadata().applicability(), + markup! { "Remove the ""style"" attribute." }.to_owned(), + mutation, + )) + } +} + +declare_node_union! { + pub NoInlineStylesQuery = AnyJsxElement | JsCallExpression +} + +fn is_style_value(value_token: &JsSyntaxToken) -> bool { + value_token.text_trimmed().to_lowercase_cow() == "style" +} + +fn find_style_object_member( + call_expression: &JsCallExpression, + model: &SemanticModel, +) -> Option { + let react_create_element = + ReactCreateElementCall::from_call_expression(call_expression, model)?; + + if react_create_element.is_custom_component() { + return None; + } + + let ReactCreateElementCall { props, .. } = react_create_element; + let props = props?; + + for member in props.members() { + if let Some(member) = member.ok() + && let Some(member) = member.as_js_property_object_member() + && let Some(name) = member.name().ok() + && let Some(name) = name.as_js_literal_member_name() + && let Some(value) = name.value().ok() + && is_style_value(&value) + { + return Some(member.clone()); + } + } + + None +} + +fn find_style_attribute(any_opening: &AnyJsxElement) -> Option { + if any_opening.is_custom_component() { + return None; + } + + for attribute in any_opening.attributes() { + if let Some(attribute) = attribute.as_jsx_attribute() + && let Some(name) = attribute.name().ok() + && let Some(name) = name.as_jsx_name() + && let Some(value_token) = name.value_token().ok() + && is_style_value(&value_token) + { + return Some(attribute.clone()); + } + } + + None +} diff --git a/crates/biome_js_analyze/src/react.rs b/crates/biome_js_analyze/src/react.rs index ebde2d098928..2ef758cdad65 100644 --- a/crates/biome_js_analyze/src/react.rs +++ b/crates/biome_js_analyze/src/react.rs @@ -95,6 +95,18 @@ impl ReactCreateElementCall { None } } + + pub fn is_custom_component(&self) -> bool { + if let Some(any_js_expression) = self.element_type.as_any_js_expression() + && let Some(literal_expression) = any_js_expression.as_any_js_literal_expression() + { + return literal_expression + .as_js_string_literal_expression() + .is_none(); + } + + true + } } impl ReactApiCall for ReactCreateElementCall { diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/invalid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/invalid.jsx new file mode 100644 index 000000000000..76f19112be4e --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/invalid.jsx @@ -0,0 +1,18 @@ +/* should generate diagnostics */ +
Error
; + +
Error
; + +; + +

Paragraph

; + +Styled; + +Image; + +React.createElement("div", { style: { color: "red" }, className: "container" }); + +React.createElement("div", { ...rest, style: { color: "red" }, className: "container" }); + +React.createElement("button", { style: { background: "blue" }, onClick: () => { } }, "Click"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/invalid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/invalid.jsx.snap new file mode 100644 index 000000000000..a8c7929d022a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/invalid.jsx.snap @@ -0,0 +1,232 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: invalid.jsx +--- +# Input +```jsx +/* should generate diagnostics */ +
Error
; + +
Error
; + +; + +

Paragraph

; + +Styled; + +Image; + +React.createElement("div", { style: { color: "red" }, className: "container" }); + +React.createElement("div", { ...rest, style: { color: "red" }, className: "container" }); + +React.createElement("button", { style: { background: "blue" }, onClick: () => { } }, "Click"); + +``` + +# Diagnostics +``` +invalid.jsx:2:6 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 1 │ /* should generate diagnostics */ + > 2 │
Error
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 3 │ + 4 │
Error
; + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 2 │ Error; + │ ------------------------ + +``` + +``` +invalid.jsx:4:16 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 2 │
Error
; + 3 │ + > 4 │
Error
; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + 5 │ + 6 │ ; + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 4 │ Error; + │ ------------------------ + +``` + +``` +invalid.jsx:6:9 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 4 │
Error
; + 5 │ + > 6 │ ; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ + 8 │

Paragraph

; + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 6 │ Click; + │ ---------------------------------------------- + +``` + +``` +invalid.jsx:8:4 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 6 │ ; + 7 │ + > 8 │

Paragraph

; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 9 │ + 10 │ Styled; + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 8 │ Paragraph

; + │ -------------------------------------- + +``` + +``` +invalid.jsx:10:7 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 8 │

Paragraph

; + 9 │ + > 10 │ Styled; + │ ^^^^^^^^^^^^^^ + 11 │ + 12 │ Image; + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 10 │ Styled; + │ -------------- + +``` + +``` +invalid.jsx:12:6 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 10 │ Styled; + 11 │ + > 12 │ Image; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + 13 │ + 14 │ React.createElement("div", { style: { color: "red" }, className: "container" }); + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 12 │ ; + │ --------------------------- + +``` + +``` +invalid.jsx:14:30 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 12 │ Image; + 13 │ + > 14 │ React.createElement("div", { style: { color: "red" }, className: "container" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 15 │ + 16 │ React.createElement("div", { ...rest, style: { color: "red" }, className: "container" }); + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 14 │ React.createElement("div",·{·style:·{·color:·"red"·},·className:·"container"·}); + │ ------------------------- + +``` + +``` +invalid.jsx:16:39 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 14 │ React.createElement("div", { style: { color: "red" }, className: "container" }); + 15 │ + > 16 │ React.createElement("div", { ...rest, style: { color: "red" }, className: "container" }); + │ ^^^^^^^^^^^^^^^^^^^^^^^ + 17 │ + 18 │ React.createElement("button", { style: { background: "blue" }, onClick: () => { } }, "Click"); + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 16 │ React.createElement("div",·{·...rest,·style:·{·color:·"red"·},·className:·"container"·}); + │ ------------------------- + +``` + +``` +invalid.jsx:18:33 lint/nursery/noInlineStyles FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + i Unexpected style attribute. + + 16 │ React.createElement("div", { ...rest, style: { color: "red" }, className: "container" }); + 17 │ + > 18 │ React.createElement("button", { style: { background: "blue" }, onClick: () => { } }, "Click"); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 19 │ + + i Inline styles make code harder to maintain, reduce reusability, and can prevent effective use of a strict Content Security Policy. Use external CSS classes or stylesheets instead. + + i This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information. + + i Unsafe fix: Remove the style attribute. + + 18 │ React.createElement("button",·{·style:·{·background:·"blue"·},·onClick:·()·=>·{·}·},·"Click"); + │ ------------------------------- + +``` diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/valid.jsx b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/valid.jsx new file mode 100644 index 000000000000..ade8184d3e6a --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/valid.jsx @@ -0,0 +1,18 @@ +/* should not generate diagnostics */ +
Error
; + +; + +

Paragraph

; + +
; + +Not inline style; + +React.createElement("div", { className: "container" }); + +React.createElement("button", { onClick: () => { } }, "Click"); + +Not inline style; + +React.createElement(Card, { style: "primary" }, "Not inline style"); diff --git a/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/valid.jsx.snap new file mode 100644 index 000000000000..7bcb235ae43b --- /dev/null +++ b/crates/biome_js_analyze/tests/specs/nursery/noInlineStyles/valid.jsx.snap @@ -0,0 +1,26 @@ +--- +source: crates/biome_js_analyze/tests/spec_tests.rs +expression: valid.jsx +--- +# Input +```jsx +/* should not generate diagnostics */ +
Error
; + +; + +

Paragraph

; + +
; + +Not inline style; + +React.createElement("div", { className: "container" }); + +React.createElement("button", { onClick: () => { } }, "Click"); + +Not inline style; + +React.createElement(Card, { style: "primary" }, "Not inline style"); + +``` diff --git a/crates/biome_rule_options/src/lib.rs b/crates/biome_rule_options/src/lib.rs index 0d38c822f153..8a9337a376c4 100644 --- a/crates/biome_rule_options/src/lib.rs +++ b/crates/biome_rule_options/src/lib.rs @@ -131,6 +131,7 @@ pub mod no_important_in_keyframe; pub mod no_important_styles; pub mod no_increment_decrement; pub mod no_inferrable_types; +pub mod no_inline_styles; pub mod no_inner_declarations; pub mod no_interactive_element_to_noninteractive_role; pub mod no_invalid_builtin_instantiation; diff --git a/crates/biome_rule_options/src/no_inline_styles.rs b/crates/biome_rule_options/src/no_inline_styles.rs new file mode 100644 index 000000000000..a15c22e288a3 --- /dev/null +++ b/crates/biome_rule_options/src/no_inline_styles.rs @@ -0,0 +1,6 @@ +use biome_deserialize_macros::{Deserializable, Merge}; +use serde::{Deserialize, Serialize}; +#[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, default)] +pub struct NoInlineStylesOptions {} diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts index 595817f907ef..28eac4560d98 100644 --- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts +++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts @@ -2167,6 +2167,11 @@ See https://biomejs.dev/linter/rules/no-increment-decrement */ noIncrementDecrement?: NoIncrementDecrementConfiguration; /** + * Disallow the use of inline styles. +See https://biomejs.dev/linter/rules/no-inline-styles + */ + noInlineStyles?: NoInlineStylesConfiguration; + /** * Disallow .bind(), arrow functions, or function expressions in JSX props. See https://biomejs.dev/linter/rules/no-jsx-props-bind */ @@ -4166,6 +4171,9 @@ export type NoHexColorsConfiguration = export type NoIncrementDecrementConfiguration = | RulePlainConfiguration | RuleWithNoIncrementDecrementOptions; +export type NoInlineStylesConfiguration = + | RulePlainConfiguration + | RuleWithNoInlineStylesOptions; export type NoJsxPropsBindConfiguration = | RulePlainConfiguration | RuleWithNoJsxPropsBindOptions; @@ -5838,6 +5846,11 @@ export interface RuleWithNoIncrementDecrementOptions { level: RulePlainConfiguration; options?: NoIncrementDecrementOptions; } +export interface RuleWithNoInlineStylesOptions { + fix?: FixKind; + level: RulePlainConfiguration; + options?: NoInlineStylesOptions; +} export interface RuleWithNoJsxPropsBindOptions { level: RulePlainConfiguration; options?: NoJsxPropsBindOptions; @@ -7436,6 +7449,7 @@ export interface NoIncrementDecrementOptions { */ allowForLoopAfterthoughts?: boolean; } +export type NoInlineStylesOptions = {}; export type NoJsxPropsBindOptions = {}; export type NoLeakedRenderOptions = {}; export type NoMisusedPromisesOptions = {}; @@ -8507,6 +8521,7 @@ export type Category = | "lint/nursery/noHexColors" | "lint/nursery/noImplicitCoercion" | "lint/nursery/noIncrementDecrement" + | "lint/nursery/noInlineStyles" | "lint/nursery/noJsxPropsBind" | "lint/nursery/noLeakedRender" | "lint/nursery/noMissingGenericFamilyKeyword" @@ -8535,6 +8550,7 @@ export type Category = | "lint/nursery/noShadow" | "lint/nursery/noSyncScripts" | "lint/nursery/noTernary" + | "lint/nursery/noTopLevelLiterals" | "lint/nursery/noUndeclaredEnvVars" | "lint/nursery/noUnknownAttribute" | "lint/nursery/noUnnecessaryConditions" @@ -8580,7 +8596,6 @@ export type Category = | "lint/nursery/useScopedStyles" | "lint/nursery/useSortedClasses" | "lint/nursery/useSpread" - | "lint/nursery/noTopLevelLiterals" | "lint/nursery/useUnicodeRegex" | "lint/nursery/useUniqueArgumentNames" | "lint/nursery/useUniqueFieldDefinitionNames" diff --git a/packages/@biomejs/biome/configuration_schema.json b/packages/@biomejs/biome/configuration_schema.json index 90e4d4ee5ae7..218e453485c5 100644 --- a/packages/@biomejs/biome/configuration_schema.json +++ b/packages/@biomejs/biome/configuration_schema.json @@ -4002,6 +4002,16 @@ "type": "object", "additionalProperties": false }, + "NoInlineStylesConfiguration": { + "oneOf": [ + { "$ref": "#/$defs/RulePlainConfiguration" }, + { "$ref": "#/$defs/RuleWithNoInlineStylesOptions" } + ] + }, + "NoInlineStylesOptions": { + "type": "object", + "additionalProperties": false + }, "NoInnerDeclarationsConfiguration": { "oneOf": [ { "$ref": "#/$defs/RulePlainConfiguration" }, @@ -6012,6 +6022,13 @@ { "type": "null" } ] }, + "noInlineStyles": { + "description": "Disallow the use of inline styles.\nSee https://biomejs.dev/linter/rules/no-inline-styles", + "anyOf": [ + { "$ref": "#/$defs/NoInlineStylesConfiguration" }, + { "type": "null" } + ] + }, "noJsxPropsBind": { "description": "Disallow .bind(), arrow functions, or function expressions in JSX props.\nSee https://biomejs.dev/linter/rules/no-jsx-props-bind", "anyOf": [ @@ -8332,6 +8349,16 @@ "additionalProperties": false, "required": ["level"] }, + "RuleWithNoInlineStylesOptions": { + "type": "object", + "properties": { + "fix": { "anyOf": [{ "$ref": "#/$defs/FixKind" }, { "type": "null" }] }, + "level": { "$ref": "#/$defs/RulePlainConfiguration" }, + "options": { "$ref": "#/$defs/NoInlineStylesOptions" } + }, + "additionalProperties": false, + "required": ["level"] + }, "RuleWithNoInnerDeclarationsOptions": { "type": "object", "properties": {