Skip to content

feat(lint): add NoInlineStyles nursery rule#9146

Closed
mcmxcdev wants to merge 2 commits intobiomejs:mainfrom
mcmxcdev:feat/add-no-inline-styles-nursery-rule
Closed

feat(lint): add NoInlineStyles nursery rule#9146
mcmxcdev wants to merge 2 commits intobiomejs:mainfrom
mcmxcdev:feat/add-no-inline-styles-nursery-rule

Conversation

@mcmxcdev
Copy link
Contributor

This PR was written primarily by Cursor.

Summary

Added the nursery rule NoInlineStyles which disallows the usage of style prop in HTML and JSX files.

Closes #9062

Test Plan

  • just test-lintrule NoInlineStyles — all spec tests pass
  • Invalid cases: style prop in use in HTML and JSX
  • Valid cases: use of class (HTML), className (JSX) data-style, id and other props

Docs

Documentation is included as rustdoc examples in the rule implementation with expect_diagnostic annotations.

@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: d5b4b48

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis L-HTML Language: HTML and super languages labels Feb 19, 2026
"@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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added the rule for HTML and JSX only so far since those are the languages we would like to have covered at Dune. Should this already be extended to Vue, Svelte and Astro? Or in a follow up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not sure if this PR is properly adding the rule in the correct way across languages like HTML and JSX.

Copy link
Contributor

@dyc3 dyc3 Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this already be extended to Vue, Svelte and Astro?

It should just work because you've already implemented it for HTML, but you can add snapshot tests for those to verify.

I am also not sure if this PR is properly adding the rule in the correct way across languages like HTML and JSX.

As long as the rule has the same name in js_analyze and html_analyze, you should be good.

@mcmxcdev mcmxcdev marked this pull request as ready for review February 21, 2026 02:23
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Walkthrough

This pull request introduces a new nursery linter rule noInlineStyles to detect inline styles across both HTML and JSX contexts. The implementation adds detection for inline style attributes on HTML elements and the style prop in JSX elements and React.createElement calls. Supporting files include rule options, test fixtures for valid and invalid cases, and a changeset documenting the patch release.

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a new NoInlineStyles nursery rule for the linter, directly matching the PR's primary objective.
Description check ✅ Passed The description is well-related to the changeset, clearly explaining the rule's purpose, test methodology, and referencing the closed issue #9062.
Linked Issues check ✅ Passed The PR successfully implements the requirement from #9062: a NoInlineStyles rule disallowing inline styles in HTML and JSX, with comprehensive test coverage and documentation.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing the NoInlineStyles rule: rule implementations, test fixtures, options definitions, and documentation—no extraneous changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs (1)

84-86: Diagnostic note is slightly inconsistent with the JSX counterpart.

The HTML rule says "Use a CSS class instead." while the JS rule (see crates/biome_js_analyze/src/lint/nursery/no_inline_styles.rs line 144) says "Use a CSS class or a styling library instead." — worth aligning for a consistent user experience.

📝 Suggested fix
-            "Use a CSS class instead."
+            "Use a CSS class or a styling library instead."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs` around lines
84 - 86, Update the diagnostic note in the no_inline_styles lint so it matches
the JS rule: replace the .note(markup! { "Use a CSS class instead." }) in
crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs with
.note(markup! { "Use a CSS class or a styling library instead." }) so the
message is consistent with the JS counterpart (see the JS rule in
crates/biome_js_analyze/src/lint/nursery/no_inline_styles.rs).
crates/biome_rule_options/src/no_inline_styles.rs (1)

3-6: Add a doc comment to NoInlineStylesOptions.

The struct is missing rustdoc, which is expected even for empty options types — it's the doc surface users see in generated references.

📝 Suggested addition
+/// Options for the [`noInlineStyles`](https://biomejs.dev/linter/rules/no-inline-styles/) rule.
 #[derive(Default, Clone, Debug, Deserialize, Deserializable, Merge, Eq, PartialEq, Serialize)]

As per coding guidelines: "Use inline rustdoc documentation for rules, assists, and their options."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/biome_rule_options/src/no_inline_styles.rs` around lines 3 - 6, Add an
inline rustdoc comment to the NoInlineStylesOptions struct explaining its
purpose and that it is an options type (even if empty) for the "no inline
styles" rule; place the doc comment immediately above the pub struct
NoInlineStylesOptions {} and include a short one- or two-sentence description
suitable for generated docs (mention it contains no configurable fields if
applicable) so rustdoc and generated references show the rule's options surface.
.changeset/add-no-inline-styles-rule.md (1)

5-5: Add an invalid code example to the changeset.

The description covers the what and why nicely, but is missing a concrete invalid snippet — required per project guidelines.

📝 Suggested addition
 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.
+
+**Invalid examples:**
+```html
+<div style="color: red;"></div>
+```
+```jsx
+<div style={{ color: "red" }}>Error</div>;
+```

Based on learnings: "For new lint rules in changesets, show an example of invalid case in inline code or code block."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/add-no-inline-styles-rule.md at line 5, The changeset for the new
nursery rule noInlineStyles is missing a required invalid code example — add
concrete invalid snippets (both HTML and JSX) inside the changeset content:
include an HTML example like a div with a style attribute (e.g., <div
style="color: red;"></div>) and a JSX example using the style prop (e.g., <div
style={{ color: "red" }}>Error</div>), formatted as an inline code block or
fenced code block so reviewers and docs clearly show the invalid cases for the
noInlineStyles rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.changeset/add-no-inline-styles-rule.md:
- Line 5: The changeset for the new nursery rule noInlineStyles is missing a
required invalid code example — add concrete invalid snippets (both HTML and
JSX) inside the changeset content: include an HTML example like a div with a
style attribute (e.g., <div style="color: red;"></div>) and a JSX example using
the style prop (e.g., <div style={{ color: "red" }}>Error</div>), formatted as
an inline code block or fenced code block so reviewers and docs clearly show the
invalid cases for the noInlineStyles rule.

In `@crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs`:
- Around line 84-86: Update the diagnostic note in the no_inline_styles lint so
it matches the JS rule: replace the .note(markup! { "Use a CSS class instead."
}) in crates/biome_html_analyze/src/lint/nursery/no_inline_styles.rs with
.note(markup! { "Use a CSS class or a styling library instead." }) so the
message is consistent with the JS counterpart (see the JS rule in
crates/biome_js_analyze/src/lint/nursery/no_inline_styles.rs).

In `@crates/biome_rule_options/src/no_inline_styles.rs`:
- Around line 3-6: Add an inline rustdoc comment to the NoInlineStylesOptions
struct explaining its purpose and that it is an options type (even if empty) for
the "no inline styles" rule; place the doc comment immediately above the pub
struct NoInlineStylesOptions {} and include a short one- or two-sentence
description suitable for generated docs (mention it contains no configurable
fields if applicable) so rustdoc and generated references show the rule's
options surface.

Copy link
Contributor

@dyc3 dyc3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A very nice first draft! Mostly nitpicks.

}

impl Rule for NoInlineStyles {
type Query = Semantic<AnyJsElementWithStyle>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why you would need the semantic model for this. It should just be a simple ban on the style attribute

sources: &[
RuleSource::HtmlEslint("no-inline-styles").inspired(),
],
domains: &[RuleDomain::React],
Copy link
Contributor

@dyc3 dyc3 Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure i would put this in the react domain

@@ -0,0 +1,15 @@
// Invalid cases - should trigger the rule
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be our magic comment /* should generate diagnostics */

@@ -0,0 +1,13 @@
<!-- Invalid cases - should trigger the rule -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be our magic comment <!-- should generate diagnostics -->

@@ -0,0 +1,11 @@
<!-- Valid cases - should NOT trigger the rule -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be our magic comment <!-- should not generate diagnostics -->

///
/// Inline styles are specified using the `style` attribute directly on an element.
/// They 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).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the bottom of the docs, or here, we should have a link to CSP. Possibly from MDN website

let name = attribute.name().ok()?;
let name_token = name.value_token().ok()?;

if name_token.text_trimmed() == "style" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attributes are case insensitive. Use to_lowercase_cow and add some tests for different variants of style e.g. STYLE

use biome_rule_options::no_inline_styles::NoInlineStylesOptions;

declare_lint_rule! {
/// Disallow the use of inline styles in JSX.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Disallow the use of inline styles in JSX.
/// Disallow the use of inline styles.

Redundant

/// ### Invalid
///
/// ```jsx,expect_diagnostic
/// <div style={{ color: "red" }}>Error</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <div style={{ color: "red" }}>Error</div>
/// <div style="color: red">Error</div>

It seems useless to cover two examples that show the same style attribute

}
}
AnyJsElementWithStyle::JsCallExpression(call_expression) => {
if let Some(react_create_element) =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is full if let Ok and let Some. Use ok() with the try operator. It will make the code easier to read

13 │ React.createElement("div", { style: { color: "red" } });
14 │
> 15 │ React.createElement("button", { style: { background: "blue" } }, "Click");
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The range here is incorrect

Some(
RuleDiagnostic::new(
rule_category!(),
state.range(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use trimmed_range

type Signals = Option<Self::State>;
type Options = NoInlineStylesOptions;

fn run(ctx: &RuleContext<Self>) -> Self::Signals {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSX rule needs more tests and logic. We shouldn't trigger the rule for Components.

@mcmxcdev did you copy the tests from the eslint rule? If not, I ask you to do so, don't let the agent take over because it doesn't know anything

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 9, 2026

Merging this PR will not alter performance

✅ 1 untouched benchmark
⏩ 155 skipped benchmarks1


Comparing mcmxcdev:feat/add-no-inline-styles-nursery-rule (226e9ce) with main (3ca066b)

Open in CodSpeed

Footnotes

  1. 155 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions github-actions bot added the A-CLI Area: CLI label Mar 9, 2026
@ematipico ematipico closed this Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-HTML Language: HTML and super languages L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 Port no-inline-styles from html-eslint

3 participants