Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

dibashthapa
Copy link

Summary

This PR migrates the no_unknown_property rule from Eslint React. This closes #7657

However, my code is not ready for a good PR yet πŸ˜…. For some reasons, I am unable to test with the options although i have them inside options.json file. This code doesn't take care of the react version to handle some edge cases.

This PR may conflict with existing no-unknown-property rule from css, and may redirect to same url.

Test Plan

The tests are included according to the spec. Valid cases are inside valid.jsx and invalid cases in invalid.jsx. For valid and invalid cases with options, I have added them in different folder. For ignore options, the code is inside ignore folder and for requireDataLowerCase, they are stored inside lowercase folder.

Docs

@changeset-bot
Copy link

changeset-bot bot commented Oct 16, 2025

πŸ¦‹ Changeset detected

Latest commit: 22e95d7

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

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

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 labels Oct 16, 2025
@dibashthapa dibashthapa changed the title Feat/no unknown property feat(lint): implement no-unknown-property from eslint react Oct 16, 2025
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 good first draft :)

Also, this should implement the ignore option for the css rule.

'@biomejs/biome': minor
---

added the new rule [`no-unknown-property`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md) from react-rules
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 also contain examples of valid/invalid code. It also should mention ignore getting added to the css rule (maybe in another changeset).

Suggested change
added the new rule [`no-unknown-property`](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md) from react-rules
Added the new rule [`noUnknownProperty`](use biome link here) from react-rules

Copy link
Author

Choose a reason for hiding this comment

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

Done, i need to check for css rule

@codspeed-hq
Copy link

codspeed-hq bot commented Oct 16, 2025

CodSpeed Performance Report

Merging #7774 will not alter performance

Comparing dibashthapa:feat/no-unknown-property (c8a3db6) with next (0086309)

Summary

βœ… 53 untouched
⏩ 85 skipped1

Footnotes

  1. 85 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. ↩

autofix-ci bot and others added 5 commits October 16, 2025 20:38
- Remove to_lowercase from most places
- Added react domain to the rule
- Removed regex and replaced with custom if checks
- Merged two constant arrays to avoid allocations
- remove TextRange from State, to use ctx.query()
- Used standard messages for diagnostics
@github-actions github-actions bot added the A-CLI Area: CLI label Oct 17, 2025
@dibashthapa dibashthapa marked this pull request as ready for review October 20, 2025 14:08
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 20, 2025

Walkthrough

This PR adds a nursery lint rule noUnknownProperty that validates JSX/DOM attribute usage. It implements the rule in no_unknown_property.rs, introduces configurable options (require_data_lowercase, ignore) via NoUnknownPropertyOptions, and adds comprehensive tests covering valid, invalid, lowercase enforcement and ignore-list scenarios.

Possibly related PRs

  • feat: promote rulesΒ #7137: Reclassifies or removes nursery rules including noUnknownPropertyβ€”directly touches the same lint rule's classification and scope.

Suggested reviewers

  • dyc3
  • mdevils

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (4 passed)
Check name Status Explanation
Title Check βœ… Passed The pull request title "feat(lint): implement no-unknown-property from eslint react" clearly and directly summarizes the main change: implementing a new lint rule migrated from ESLint React. The title is concise, uses proper conventional commit format, and accurately reflects the changeset content without unnecessary noise. It provides enough specificity that a teammate reviewing history would immediately understand this introduces a new linting rule.
Linked Issues Check βœ… Passed The implementation successfully delivers the core objective from issue #7657: a new noUnknownProperty lint rule has been implemented with comprehensive validation logic for JSX/DOM attributes. The rule includes support for data attributes, ARIA attributes, allowed tag constraints, and diagnostic messaging. The options structure (require_data_lowercase and ignore list) is in place, and extensive test coverage spans valid cases, invalid cases, and option-specific scenarios. Whilst the author notes that React version edge cases aren't yet addressed, the foundational implementation meets the stated requirement to port the rule from ESLint React.
Out of Scope Changes Check βœ… Passed All code changes remain in scope and directly support the implementation of the no-unknown-property rule. The changeset file documents the new rule, the lint rule implementation provides the core logic, the options structure supports configuration, and test files comprehensively cover valid/invalid cases and option-specific scenarios. No extraneous changes or unrelated modifications are present in the changeset.
Description Check βœ… Passed The pull request description is relevant to the changeset and provides meaningful context: it explains the rule migration from ESLint React, references the related issue (#7657), outlines the test plan with valid/invalid cases and option-specific tests, and honestly acknowledges current limitations (options not being applied correctly, React version edge cases not yet handled). The level of detail is appropriate for understanding the PR's purpose and scope.
✨ Finishing touches
  • πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
.changeset/small-words-show.md (1)

5-5: Tighten the changeset copy, add examples, and fix the link.

  • Use past tense, proper naming (β€œeslint‑plugin‑react”), and end sentences with a period.
  • Add tiny valid/invalid examples.
  • Point to the correct rule page; the current slug likely collides with the CSS rule page. Update once the docs page exists.

Apply this diff:

-Added the new rule [`no-unknown-property`](https://biomejs.dev/linter/rules/no-unknown-property/)  from eslint react
+#### Lint rules.
+
+Added the new nursery rule [`noUnknownProperty`](<update-with-correct-docs-url>) ported from `eslint-plugin-react`.
+
+Examples:
+
+```jsx
+/* valid */
+<App class="foo" /><div data-foo="bar" />
+```
+
+```jsx
+/* invalid */
+<div class="foo" /><div data-testID="bar" />
+```

As per coding guidelines.

🧹 Nitpick comments (10)
crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx (1)

1-6: Nice focused β€œignore” case.

Consider adding a sibling negative case (property not on the ignore list) to prove the rule still fires.

crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx (1)

1-4: Confirm parity on custom components.

Do we intend requireDataLowercase to apply to custom components like <App />? ESLint’s rule often limits DOM‑prop checks to host elements. If we want parity, scope this to intrinsic elements; if we want stricter Biome behaviour, keep as is and document it.

crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.jsx (1)

1-204: Big omnibus β€œvalid” test – consider splitting by theme.

Smaller themed fixtures (events, SVG, boolean attrs, data/aria, legacy attrs) make diffs and failures easier to triage. Also keep the ReactΒ 19 popover block gated in a dedicated future test.

Quick sweep for potential tag‑specific mismatches (e.g. allowFullScreen on non‑iframe) would be good to double‑check against the rule’s allowed‑tags map.

crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/invalid.jsx (2)

21-38: Add popover/onBeforeToggle cases to lock React 19 support

Please add invalid/valid cases for popover API:

  • Attributes: popover, popovertarget, popovertargetaction.
  • Events: onBeforeToggle/onToggle on allowed tags.
    This prevents regressions and aligns with your commented code in the rule.

1-3: Consider a companion valid case for custom elements

Add a valid fixture using a custom element (e.g., ) to assert the rule skips custom elements and tags with an is attribute. This documents intent.

crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (5)

1065-1077: Avoid avoidable allocations for attribute names

name/value_token().text_trimmed().to_string() and the manual namespace concatenation allocate on every attribute. Use TokenText and Cow to allocate only for namespaced cases.

@@
-        let node_name = match node.name().ok()? {
-            AnyJsxAttributeName::JsxName(name) => {
-                name.value_token().ok()?.text_trimmed().to_string()
-            }
-            AnyJsxAttributeName::JsxNamespaceName(name) => {
-                let namespace = name.namespace().ok()?.value_token().ok()?;
-                let name = &name.name().ok()?.value_token().ok()?;
-                // There could be better way, but i couldn't extract namespaced attributes
-                // For e.g xlink:href
-                // without manually concatenating with ':'
-                namespace.text_trimmed().to_string() + ":" + name.text_trimmed()
-            }
-        };
+        use std::borrow::Cow;
+        let node_name: Cow<'_, str> = match node.name().ok()? {
+            AnyJsxAttributeName::JsxName(name) => {
+                Cow::Borrowed(name.value_token().ok()?.text_trimmed())
+            }
+            AnyJsxAttributeName::JsxNamespaceName(name) => {
+                let ns = name.namespace().ok()?.value_token().ok()?.text_trimmed();
+                let local = name.name().ok()?.value_token().ok()?.text_trimmed();
+                Cow::Owned(format!("{ns}:{local}"))
+            }
+        };
@@
-        if options.ignore.contains(&node_name) {
+        if options.ignore.iter().any(|s| s == &*node_name) {
             return None;
         }
@@
-        let name = normalize_attribute_case(&node_name);
+        let name = normalize_attribute_case(&node_name);

Also applies to: 1079-1081


1009-1014: Simplify tag_name_has_dot signature

This always returns Some(..). Make it return bool and drop ? at call sites; avoids needless Option churn.

-fn tag_name_has_dot(node: &AnyJsxElement) -> Option<bool> {
-    Some(matches!(
-        node.name().ok()?,
-        AnyJsxElementName::JsxMemberName(_)
-    ))
-}
+fn tag_name_has_dot(node: &AnyJsxElement) -> bool {
+    matches!(node.name().ok(), Ok(AnyJsxElementName::JsxMemberName(_)))
+}
@@
-        if tag_name_has_dot(&element)? {
+        if tag_name_has_dot(&element) {
             return None;
         }

1167-1183: Minor copy tweak

The diagnostic says β€œfor React components”, but this rule validates DOM elements after gating out components. Suggest β€œin JSX” to avoid confusion.

-                    "Use '"{standard_name}"' instead of '"{name}"' for React components."
+                    "Use '"{standard_name}"' instead of '"{name}"' in JSX."

1484-1510: Use case‑insensitive check for data‑ only after the prefix*

Current has_uppercase(name) flags any uppercase anywhere, which is fine, but the option intent is about the part after data-. Consider restricting the scan to the suffix to keep the message crisp for odd inputs.

-            if options.require_data_lowercase && has_uppercase(&name) {
+            if options.require_data_lowercase {
+                let suffix = &name["data-".len()..];
+                if has_uppercase(suffix) {
                     return Some(NoUnknownPropertyDiagnostic::DataLowercaseRequired {
                         name: name.to_string(),
                         lowercase_name: name.to_lowercase(),
                     });
-            }
+                }
+            }

226-231: Keys are sorted β€” guard test is defensive, not critical

The ATTRIBUTE_TAGS_MAP keys are currently sorted and the code is correct. However, the suggestion to add a guard test is reasonable: without one, future edits could accidentally break sort order without triggering a compile-time error, and binary_search_by_key would silently fail at runtime.

Consider adding a simple unit test at the end of the file to assert the map remains sorted:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn attribute_tags_map_is_sorted() {
        let keys: Vec<_> = ATTRIBUTE_TAGS_MAP.iter().map(|(k, _)| k).collect();
        let mut sorted = keys.clone();
        sorted.sort();
        assert_eq!(keys, sorted, "ATTRIBUTE_TAGS_MAP keys must remain sorted for binary_search_by_key");
    }
}
πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 0086309 and 91d7893.

β›” Files ignored due to path filters (11)
  • crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs is excluded by !**/migrate/eslint_any_rule_to_biome.rs and included by **
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/domain_selector.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_js_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx.snap is excluded by !**/*.snap and included by **
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.jsx.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
  • packages/@biomejs/biome/configuration_schema.json is excluded by !**/configuration_schema.json and included by **
πŸ“’ Files selected for processing (10)
  • .changeset/small-words-show.md (1 hunks)
  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.jsx (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.package.json (1 hunks)
  • crates/biome_rule_options/src/no_unknown_property.rs (1 hunks)
🧰 Additional context used
πŸ““ Path-based instructions (5)
crates/biome_*/**

πŸ“„ CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_rule_options/src/no_unknown_property.rs
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/invalid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.package.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx
  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
**/*.rs

πŸ“„ CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Format Rust files before committing (e.g., via just f which formats Rust)
Document rules, assists, and options with inline rustdoc in source

Files:

  • crates/biome_rule_options/src/no_unknown_property.rs
  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

πŸ“„ CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/invalid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.package.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx
  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
**/tests/**

πŸ“„ CodeRabbit inference engine (CLAUDE.md)

Place test files under a tests/ directory in each crate

Files:

  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/invalid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.package.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx
.changeset/*.md

πŸ“„ CodeRabbit inference engine (CONTRIBUTING.md)

.changeset/*.md: In changesets, only use #### or ##### headers; other header levels are not allowed
Changesets should cover user-facing changes only; internal changes do not need changesets
Use past tense for what you did and present tense for current Biome behavior in changesets
When fixing a bug in a changeset, start with an issue link (e.g., β€œFixed #1234: …”)
When referencing a rule or assist in a changeset, include a link to its page on the website
Include code blocks in changesets when applicable to illustrate changes
End every sentence in a changeset with a period

Files:

  • .changeset/small-words-show.md
🧠 Learnings (6)
πŸ“š Learning: 2025-10-15T09:20:19.139Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:20:19.139Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : For options types, derive Serialize, Deserialize, Deserializable (and JsonSchema under the schema feature) and use #[serde(rename_all="camelCase", deny_unknown_fields, default)] with skip_serializing_if where appropriate

Applied to files:

  • crates/biome_rule_options/src/no_unknown_property.rs
πŸ“š Learning: 2025-10-15T09:22:15.851Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:22:15.851Z
Learning: Applies to crates/biome_formatter/tests/specs/**/options.json : Use options.json files colocated with test inputs to override formatting options for all files in that folder

Applied to files:

  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json
πŸ“š Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
PR: biomejs/biome#7081
File: packages/@biomejs/biome/configuration_schema.json:7765-7781
Timestamp: 2025-08-05T14:43:29.581Z
Learning: The file `packages/biomejs/biome/configuration_schema.json` is auto-generated and should not be manually edited or reviewed for schema issues; any changes should be made at the code generation source.

Applied to files:

  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json
πŸ“š Learning: 2025-10-15T09:20:19.139Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:20:19.139Z
Learning: Applies to crates/biome_analyze/**/tests/specs/**/*.{js,jsx,ts,tsx} : Place snapshot test cases under tests/specs/<group>/<ruleName>/ with files typically prefixed by invalid/ or valid/

Applied to files:

  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.jsx
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.package.json
  • crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.jsx
πŸ“š Learning: 2025-10-15T09:20:19.139Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:20:19.139Z
Learning: A rule’s diagnostic should explain what the error is, why it’s triggered, and what to do (ideally via a code action or a note)

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
πŸ“š Learning: 2025-10-15T09:21:24.116Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:21:24.116Z
Learning: A diagnostic must explain why something went wrong, using log advices and links to documentation when helpful

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
🧬 Code graph analysis (2)
crates/biome_rule_options/src/no_unknown_property.rs (1)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (1)
  • NoUnknownPropertyOptions (8160-8163)
crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (2)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (2)
  • RuleDomain (927-935)
  • NoUnknownPropertyOptions (8160-8163)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
πŸ”‡ Additional comments (4)
crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/ignore/valid.options.json (1)

1-16: No issues identified; convention verified.

The per-file naming pattern (<basename>.options.json) is indeed the established convention across analyser specsβ€”over 20 examples confirm this, including other noUnknownProperty subfolder tests. The review comment is accurate.

crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/valid.package.json (1)

1-6: Remove or document the React 16.4.0 fixture as a placeholder.

The rule currently does not consult React versions (no version gating logic exists in the implementation). The commented-out POPOVER_API_PROPS constant suggests this is planned. Either drop the fixture file to avoid confusion, or add a comment documenting that it's a placeholder for future version-gated testing.

crates/biome_js_analyze/tests/specs/nursery/noUnknownProperty/lowercase/invalid.options.json (1)

1-17: Verified: Options structure and JSON serialisation are correct.

The struct at crates/biome_rule_options/src/no_unknown_property.rs has the expected serde attributes: #[serde(rename_all = "camelCase", deny_unknown_fields, default)] with proper derives (Serialize, Deserialize, Deserializable, JsonSchema). The field require_data_lowercase correctly serialises to "requireDataLowercase" via camelCase conversion, matching the test JSON precisely. Pattern confirmed across 174 .options.json files in the specs directory.

crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (1)

56-87: Code already correctly configuredβ€”no changes needed

The struct in crates/biome_rule_options/src/no_unknown_property.rs already has the proper serde setup:

  • #[serde(rename_all = "camelCase", deny_unknown_fields, default)] is present βœ“
  • Rust field require_data_lowercase (snake_case) correctly maps to JSON requireDataLowercase via the bulk rename
  • Documentation uses the correct camelCase field names
  • Field ignore properly uses #[serde(skip_serializing_if = "Vec::is_empty")]

This follows the established pattern for options types in the codebase. The serde rename mechanism is already working as intended.

Comment on lines +701 to +713
"onAbort",
"onAbortCapture",
"onAnimationEnd",
"onAnimationEndCapture",
"onAnimationIteration",
"onAnimationStart",
"onAnimationStartCapture",
"onAuxClick",
"onAuxClickCapture",
"onBeforeInput",
"onBeforeInputCapture",
"onbeforetoggle",
"onBlur",
Copy link
Contributor

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion | 🟠 Major

Incorrect/ambiguous event casing: include onBeforeToggle (React 19)

DOM_PROPERTY_NAMES contains onToggle and ontoggle, but only onbeforetoggle (lowercase) for the pre‑toggle event. Canonical React prop is onBeforeToggle. Without it, kebab/incorrect casing won’t be auto‑fixed.

Proposed fix:

  • Add "onBeforeToggle".
  • Keep "onbeforetoggle" only if you intend to recognise legacy HTML attribute casing; otherwise remove it to prefer camelCase.
@@
-    "onbeforetoggle",
+    "onBeforeToggle",
@@
-    "ontoggle",
+    "onToggle",

Add tests in specs for onbeforetoggle β†’ onBeforeToggle and ontoggle β†’ onToggle on valid/invalid tags.

Also applies to: 846-848

πŸ€– Prompt for AI Agents
In crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs around lines
701 to 713, the list of DOM property/event names is missing the canonical React
camelCase "onBeforeToggle" (it currently only contains the lowercase
"onbeforetoggle"), which prevents auto-fixing incorrect casing; add
"onBeforeToggle" to the array (you may keep "onbeforetoggle" only if you
intentionally want to recognize legacy HTML attribute casing), and update the
test specs around lines 846-848 to include cases asserting that "onbeforetoggle"
β†’ "onBeforeToggle" and "ontoggle" β†’ "onToggle" are handled as valid/invalid
where appropriate.

Comment on lines +6 to +10
pub struct NoUnknownPropertyOptions {
pub require_data_lowercase: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub ignore: Vec<String>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

Add rustdoc and check schema optionality for ignore.

  • Please document the struct and fields (what each option does, defaults).
  • Ensure the generated JSON schema doesn’t make ignore required; TS shows it as required today.

Proposed doc comments:

 #[serde(rename_all = "camelCase", deny_unknown_fields, default)]
 pub struct NoUnknownPropertyOptions {
-    pub require_data_lowercase: bool,
+    /// Enforce lower-case `data-*` attribute names when true. Default: false.
+    pub require_data_lowercase: bool,
     #[serde(skip_serializing_if = "Vec::is_empty")]
-    pub ignore: Vec<String>,
+    /// Additional attribute names to ignore (case-sensitive). Default: [].
+    pub ignore: Vec<String>,
 }

Verify schema/TS:


🏁 Script executed:

#!/bin/bash
# After codegen, check required fields under noUnknownProperty
jq -r '
  .. | objects | select(has("title") and .title=="noUnknownProperty") |
  .allOf[]?.then?.properties? // empty
' packages/@biomejs/biome/configuration_schema.json

rg -n 'interface NoUnknownPropertyOptions' packages/@biomejs/backend-jsonrpc/src/workspace.ts -n -A3

Length of output: 117


🏁 Script executed:

#!/bin/bash
# Check current state of the Rust file to see if rustdoc is present
cat crates/biome_rule_options/src/no_unknown_property.rs

Length of output: 456


Add rustdoc and fix schema optionality for ignore.

Confirmed: the struct lacks documentation, and the TypeScript interface incorrectly marks ignore as required. The #[serde(skip_serializing_if = "Vec::is_empty")] attribute means it should be optional in the schema.

Implement the proposed rustdoc additions and verify the codegen pipeline regenerates the TypeScript interface with ignore?: string[].

πŸ€– Prompt for AI Agents
In crates/biome_rule_options/src/no_unknown_property.rs around lines 6–10, add
rustdoc comments describing the struct and its fields, and make the `ignore`
field optional for schema generation by changing its type to Option<Vec<String>>
and updating serde attributes to use a default and skip serialization when None
(e.g. add #[serde(default, skip_serializing_if = "Option::is_none")]). After
updating the doc comments and the field type/serde attrs, run the codegen
pipeline to regenerate the TypeScript interface so it emits `ignore?: string[]`.

@dibashthapa
Copy link
Author

@dyc3 hey thank you for your comments, i have worked on your suggestions, and I think they are resolved.

I believe these are the things I need to work on next

  • Support for different props based on react version
  • Docstring coverage (based on rabbitai feedback)

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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (1)

713-714: Event casing: add onBeforeToggle; drop ontoggle; prefer camelCase suggestions

Current list includes lowercase "onbeforetoggle" and "ontoggle", but misses the canonical React props "onBeforeToggle" and (already present) "onToggle". As written, ontoggle/onbeforetoggle will be accepted and won’t auto‑fix to camelCase.

-    "onbeforetoggle",
+    "onBeforeToggle",
@@
-    "ontoggle",
+    // Intentionally omit lowercase "ontoggle" so it maps to "onToggle"

Add tests asserting:

  • <div onbeforetoggle={() => {}} /> β†’ diagnostic suggesting onBeforeToggle
  • <div ontoggle={() => {}} /> β†’ diagnostic suggesting onToggle

This keeps behaviour aligned with React 19’s events and improves auto‑fix.

Also applies to: 847-848

🧹 Nitpick comments (3)
crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (3)

1065-1077: Avoid unnecessary allocations for attribute names; make ignore check case‑insensitive

You allocate for every attribute (to_string, manual concat). We can avoid allocations in the common case and only allocate for namespaced attributes, while matching ignore without allocating.

+use std::borrow::Cow;
@@
-        let node_name = match node.name().ok()? {
-            AnyJsxAttributeName::JsxName(name) => {
-                name.value_token().ok()?.text_trimmed().to_string()
-            }
-            AnyJsxAttributeName::JsxNamespaceName(name) => {
-                let namespace = name.namespace().ok()?.value_token().ok()?;
-                let name = &name.name().ok()?.value_token().ok()?;
-                // There could be better way, but i couldn't extract namespaced attributes
-                // For e.g xlink:href
-                // without manually concatenating with ':'
-                namespace.text_trimmed().to_string() + ":" + name.text_trimmed()
-            }
-        };
+        let node_name_text: Cow<'_, str> = match node.name().ok()? {
+            AnyJsxAttributeName::JsxName(name) => {
+                Cow::Borrowed(name.value_token().ok()?.text_trimmed())
+            }
+            AnyJsxAttributeName::JsxNamespaceName(name) => {
+                let ns = name.namespace().ok()?.value_token().ok()?.text_trimmed();
+                let nm = name.name().ok()?.value_token().ok()?.text_trimmed();
+                Cow::Owned(format!("{ns}:{nm}"))
+            }
+        };
@@
-        if options.ignore.contains(&node_name) {
+        if options
+            .ignore
+            .iter()
+            .any(|s| s.eq_ignore_ascii_case(node_name_text.as_ref()))
+        {
             return None;
         }
@@
-        let name = normalize_attribute_case(&node_name);
+        let name = normalize_attribute_case(node_name_text.as_ref());

This removes hot‑path allocations and makes ignore friendlier. If ignore is meant to be case‑sensitive, drop eq_ignore_ascii_case.

Also applies to: 1079-1084


972-973: Redundant use inside function

use biome_string_case::StrOnlyExtension; is already imported at the module level. Remove the inner use.

-    use biome_string_case::StrOnlyExtension;

1169-1177: Offer quick‑fixes in diagnostics (rename)

You already tell users what to do; wire in code actions to auto‑rename to standard_name and lower‑case data-* attributes.

Happy to sketch the .action plumbing if useful.

Also applies to: 1205-1213

πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 91d7893 and 22e95d7.

πŸ“’ Files selected for processing (1)
  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (1 hunks)
🧰 Additional context used
πŸ““ Path-based instructions (3)
crates/biome_*_{syntax,parser,formatter,analyze,factory,semantic}/**

πŸ“„ CodeRabbit inference engine (CLAUDE.md)

Maintain the per-language crate structure: biome_{lang}_{syntax,parser,formatter,analyze,factory,semantic}

Files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
crates/biome_*/**

πŸ“„ CodeRabbit inference engine (CLAUDE.md)

Place core crates under /crates/biome_*/

Files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
**/*.rs

πŸ“„ CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Format Rust files before committing (e.g., via just f which formats Rust)
Document rules, assists, and options with inline rustdoc in source

Files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
🧠 Learnings (2)
πŸ“š Learning: 2025-10-15T09:20:19.139Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:20:19.139Z
Learning: A rule’s diagnostic should explain what the error is, why it’s triggered, and what to do (ideally via a code action or a note)

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
πŸ“š Learning: 2025-10-15T09:21:24.116Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_diagnostics/CONTRIBUTING.md:0-0
Timestamp: 2025-10-15T09:21:24.116Z
Learning: A diagnostic must explain why something went wrong, using log advices and links to documentation when helpful

Applied to files:

  • crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (3)
packages/@biomejs/backend-jsonrpc/src/workspace.ts (2)
  • RuleDomain (927-935)
  • NoUnknownPropertyOptions (8160-8163)
crates/biome_analyze/src/rule.rs (4)
  • domains (625-628)
  • sources (610-613)
  • same (246-251)
  • recommended (595-598)
crates/biome_rowan/src/ast/mod.rs (1)
  • cast_ref (142-151)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: triage
  • GitHub Check: Validate PR title
πŸ”‡ Additional comments (3)
crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs (3)

73-89: No issues foundβ€”serde mapping is correctly configured

The NoUnknownPropertyOptions struct already includes #[serde(rename_all = "camelCase", deny_unknown_fields, default)], which ensures the Rust field require_data_lowercase serialises as requireDataLowercase in JSON, matching the documentation. All coding guidelines are satisfied.


95-100: The review comment is incorrectβ€”both RuleSource variants are valid and active

Both EslintReactX and EslintReactXyz are legitimate, properly-documented variants in the RuleSource enum (lines 125 and 127 of crates/biome_analyze/src/rule.rs). They represent distinct ESLint React plugins with different outputs in the Display trait:

  • EslintReactX displays as "eslint-plugin-react-x"
  • EslintReactXyz displays as "@eslint-react/eslint-plugin"

The code compiles and is correct as written. The suggestion to consolidate to a single EslintReact source would lose important provenance information about which tool each rule originates from.

Likely an incorrect or invalid review comment.


114-225: All constants are correctly sortedβ€”no action needed.

The verification confirms that ATTRIBUTE_TAGS_MAP, DOM_ATTRIBUTE_NAMES, and SVGDOM_ATTRIBUTE_NAMES are all properly sorted by their first keys. Binary search will work as expected; the code is sound.

Comment on lines +877 to +880
"popover",
"popovertarget",
"popovertargetaction",
"preserveAlpha",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Popover API props should be camelCase (React 19)

"popovertarget" and "popovertargetaction" should be "popoverTarget" and "popoverTargetAction" so that lowercase inputs get mapped and suggested correctly.

-    "popovertarget",
-    "popovertargetaction",
+    "popoverTarget",
+    "popoverTargetAction",

Consider adding tests for:

  • <button popovertarget="id" /> β†’ suggest popoverTarget
  • <button popovertargetaction="show" /> β†’ suggest popoverTargetAction
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"popover",
"popovertarget",
"popovertargetaction",
"preserveAlpha",
"popover",
"popoverTarget",
"popoverTargetAction",
"preserveAlpha",
πŸ€– Prompt for AI Agents
In crates/biome_js_analyze/src/lint/nursery/no_unknown_property.rs around lines
877-880, the list of Popover API props includes "popovertarget" and
"popovertargetaction" in lowercase; update them to the camelCase versions
"popoverTarget" and "popoverTargetAction" so the linter maps lowercase inputs to
the correct suggestions, and add unit tests covering <button popovertarget="id"
/> β†’ suggest popoverTarget and <button popovertargetaction="show" /> β†’ suggest
popoverTargetAction to verify behavior.

name.value_token().ok()?.text_trimmed().to_string()
}
AnyJsxAttributeName::JsxNamespaceName(name) => {
let namespace = name.namespace().ok()?.value_token().ok()?;
Copy link
Author

@dibashthapa dibashthapa Oct 20, 2025

Choose a reason for hiding this comment

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

@dyc3 And this issue as well, I couldn't figure out the method to have attributes with colon , something like xmlns:href from the element, and I had to manually concatenate : and convert to String

Copy link
Contributor

Choose a reason for hiding this comment

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

You could put namespaced attributes into a different map, and that would let you avoid the string allocation.

///
pub NoUnknownProperty {
version: "next",
name: "noUnknownProperty",
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking that this actually isn't a good name for the rule. The CSS concept for "property" and react's concept for "property" are rather different.

We already call react props "Jsx Attribute". I think it would make more sense to name this rule noUnknownAttribute.

Copy link
Author

@dibashthapa dibashthapa Oct 20, 2025

Choose a reason for hiding this comment

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

I’m not sure if I actually read it somewhere or if I’m just misremembering, but I recall reading that we should keep the rule name the same for easier migration ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I did say previously that we should keep the same rule name. I have taken a closer look at what this rule does, and what the css rule of the same name does, and have changed my opinion.

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I will change the name then

#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
pub struct NoUnknownPropertyOptions {}
pub struct NoUnknownPropertyOptions {
pub require_data_lowercase: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove require_data_lowercase and match the default behavior of the source rule (which is true iirc)

Copy link
Author

Choose a reason for hiding this comment

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

the default behavior is set to false.
https://eslint-react.xyz/docs/rules/dom-no-unknown-property#rule-options

But, I am not sure why do you want to remove it ?

Copy link
Contributor

Choose a reason for hiding this comment

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

We discussed this in the original issue: #7657 (comment)
but I apologize for not making it more clear: we don't implement options without a clear use case for it. The ignore option is fine, because it allows users an immediate escape hatch, and its trivial to implement. It's a mistake we have experienced with other rules.

You are right that the source rule has the default for this as false. Idk where I read that it was true from because I can't find it now. The more important thing is that we follow the defaults of the source rule as much as it makes sense.

Copy link
Author

Choose a reason for hiding this comment

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

@dyc3 so, I should keep the ignore option ? Sorry, I just wanted to confirm

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, keep ignore

// "onBeforeToggle",
// ];

const ATTRIBUTE_TAGS_MAP: &[(&str, &[&str])] = &[
Copy link
Contributor

Choose a reason for hiding this comment

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

You could use LazyLock to let this be an actual HashMap, or FxHashMap. Not sure which would be best for this use case.

Copy link
Author

Choose a reason for hiding this comment

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

@dyc3 I will run benchmarks on this first and then let you know

name.value_token().ok()?.text_trimmed().to_string()
}
AnyJsxAttributeName::JsxNamespaceName(name) => {
let namespace = name.namespace().ok()?.value_token().ok()?;
Copy link
Contributor

Choose a reason for hiding this comment

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

You could put namespaced attributes into a different map, and that would let you avoid the string allocation.

@@ -0,0 +1,5 @@
---
'@biomejs/biome': minor
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
'@biomejs/biome': minor
'@biomejs/biome': patch

Copy link
Author

Choose a reason for hiding this comment

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

And should I open this PR against next or main branch ?

Copy link
Contributor

Choose a reason for hiding this comment

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

New nursery rules can go in patch releases. You don't have to close this PR to change the base branch of it.

Copy link
Author

Choose a reason for hiding this comment

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

Sure

@ematipico ematipico deleted the branch biomejs:next October 23, 2025 10:03
@ematipico ematipico closed this Oct 23, 2025
@ematipico ematipico reopened this Oct 23, 2025
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-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants