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

Skip to content

Conversation

@dyc3
Copy link
Contributor

@dyc3 dyc3 commented Nov 13, 2025

Summary

This PR adds noVueVIfWithVFor, and useVueHyphenatedAttributes.

It also fixes a parsing bug that I found while working on these rules, and implements html lint rules actually being able to receive options from configuration (it wasn't plumbed up before).

Test Plan

Added snapshot tests.

Docs

@changeset-bot
Copy link

changeset-bot bot commented Nov 13, 2025

🦋 Changeset detected

Latest commit: 95ddb39

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 A-Diagnostic Area: diagnostocis L-HTML Language: HTML and super languages labels Nov 13, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Nov 13, 2025

CodSpeed Performance Report

Merging #8097 will not alter performance

Comparing dyc3/vue-high-value-rules (95ddb39) with main (47d940e)

Summary

✅ 1 untouched
⏩ 154 skipped1

Footnotes

  1. 154 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.

@dyc3 dyc3 force-pushed the dyc3/vue-high-value-rules branch from 999910c to b3187b8 Compare November 13, 2025 18:46
@github-actions github-actions bot added the A-Parser Area: parser label Nov 13, 2025
@dyc3 dyc3 added the D-Vue Domains: Vue label Nov 13, 2025
@dyc3 dyc3 force-pushed the dyc3/vue-high-value-rules branch 2 times, most recently from bb7a5b1 to c65e860 Compare November 14, 2025 16:28
@dyc3 dyc3 marked this pull request as ready for review November 14, 2025 16:29
@dyc3 dyc3 requested review from a team November 14, 2025 16:29
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 14, 2025

Walkthrough

Adds two Vue-focused HTML lints and supporting infra: NoVueVIfWithVFor (flags v-if and v-for on the same element) and UseVueHyphenatedAttributes (enforce kebab-case attribute names). Implements diagnostics and a code-action for attribute renames, adds rule option types and changeset entries, extends the HTML lexer to recognise Vue dynamic-argument brackets ([ / ]), updates parser options for Vue, and wires HTML lint metadata into the service/analyser configuration with corresponding tests and fixtures.

Possibly related PRs

Suggested reviewers

  • mdevils
  • arendjr

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding two Vue linting rules (noVueVIfWithVFor and useVueHyphenatedAttributes) to the linter.
Description check ✅ Passed The description clearly explains the PR's purpose: adding two Vue rules, fixing a parsing bug, and implementing configuration plumbing for HTML lint rules.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dyc3/vue-high-value-rules

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: 1

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

196-200: Consider refactoring for clarity (optional).

The double-negative logic here (if NOT (v-bind or v-model) then return None) is functionally correct but slightly harder to read. Consider a positive guard instead:

-            if let Ok(name) = directive.name_token().map(|name| name.token_text_trimmed())
-                && !(name == "v-bind" || name == "v-model")
-            {
-                return None;
-            }
+            if let Ok(name) = directive.name_token().map(|name| name.token_text_trimmed())
+                && name != "v-bind" && name != "v-model"
+            {
+                return None;
+            }

Or restructure as:

if let Ok(name) = directive.name_token().map(|name| name.token_text_trimmed()) {
    if name != "v-bind" && name != "v-model" {
        return None;
    }
}
📜 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 1fdcaf0 and c65e860.

⛔ Files ignored due to path filters (18)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_html_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-dynamic.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.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 (17)
  • .changeset/evil-experts-repeat.md (1 hunks)
  • .changeset/great-mammals-hide.md (1 hunks)
  • .changeset/loose-chairs-wonder.md (1 hunks)
  • crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (1 hunks)
  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (1 hunks)
  • crates/biome_html_analyze/tests/spec_tests.rs (3 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/options.json (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue (1 hunks)
  • crates/biome_html_parser/src/lexer/mod.rs (2 hunks)
  • crates/biome_html_parser/tests/quick_test.rs (1 hunks)
  • crates/biome_rule_options/src/lib.rs (2 hunks)
  • crates/biome_rule_options/src/no_vue_v_if_with_v_for.rs (1 hunks)
  • crates/biome_rule_options/src/use_vue_hyphenated_attributes.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-25T07:02:26.457Z
Learnt from: ematipico
Repo: biomejs/biome PR: 7856
File: .changeset/yellow-crews-guess.md:1-5
Timestamp: 2025-10-25T07:02:26.457Z
Learning: The Biome documentation website uses kebab-case URL slugs for rule pages (e.g., `/linter/rules/no-continue/`), not camelCase.

Applied to files:

  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs
  • .changeset/great-mammals-hide.md
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
Repo: biomejs/biome PR: 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_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/options.json
  • .changeset/great-mammals-hide.md
📚 Learning: 2025-09-25T12:32:59.003Z
Learnt from: arendjr
Repo: biomejs/biome PR: 7593
File: crates/biome_service/src/workspace/server.rs:1306-1306
Timestamp: 2025-09-25T12:32:59.003Z
Learning: In the biomejs/biome project, do not flag compilation errors during code review as they are handled by the existing test infrastructure and CI. Focus on other code quality aspects instead.

Applied to files:

  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/options.json
🧬 Code graph analysis (4)
crates/biome_html_parser/tests/quick_test.rs (1)
crates/biome_html_parser/src/parser.rs (1)
  • options (32-34)
crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (2)
crates/biome_analyze/src/rule.rs (3)
  • domains (632-635)
  • sources (617-620)
  • same (246-251)
crates/biome_string_case/src/lib.rs (1)
  • identify (112-175)
crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (2)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • domains (632-635)
  • sources (617-620)
  • same (246-251)
crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (2)
  • run (64-90)
  • diagnostic (92-108)
crates/biome_html_analyze/tests/spec_tests.rs (1)
crates/biome_html_parser/src/lib.rs (1)
  • parse_html (37-40)
🪛 LanguageTool
.changeset/loose-chairs-wonder.md

[style] ~4-~4: Consider using a different verb for a more formal wording.
Context: --- "@biomejs/biome": patch --- Fixed an issue with the HTML parser where it ...

(FIX_RESOLVE)

⏰ 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). (9)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Documentation
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: End-to-end tests
  • GitHub Check: Check Dependencies
  • GitHub Check: Check JS Files
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_configuration)
🔇 Additional comments (23)
crates/biome_html_parser/tests/quick_test.rs (1)

8-11: LGTM! Vue dynamic argument syntax test updated correctly.

The test now exercises Vue's dynamic directive argument syntax (:[dynamicArg]), and the options correctly enable Vue parsing mode. Since this is an ignored quick test, it serves its purpose as a developer scratch pad.

.changeset/evil-experts-repeat.md (1)

1-5: LGTM!

The changeset is clear and follows the standard format.

crates/biome_html_parser/src/lexer/mod.rs (2)

136-143: Good addition for Vue dynamic directive arguments.

The bracket token support enables parsing of Vue's dynamic directive syntax (e.g., v-bind:[key]).


1078-1078: Correctly excludes Vue-specific characters from attribute names.

This ensures Vue directive special characters (:, ., [, ]) are tokenised separately rather than as part of attribute names.

crates/biome_html_analyze/tests/spec_tests.rs (1)

154-154: Good refactoring for Vue-aware parsing.

Deriving parse options from the source type ensures Vue files are re-parsed with the correct Vue-specific lexer context.

Also applies to: 182-182

.changeset/loose-chairs-wonder.md (1)

1-5: LGTM!

The changeset clearly documents the parser fix for Vue directive argument handling.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/options.json (1)

1-21: LGTM!

The test configuration correctly exercises the rule's ignore options.

crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue (1)

1-13: LGTM!

The test fixtures correctly demonstrate both orderings of the v-if/v-for anti-pattern.

crates/biome_rule_options/src/lib.rs (1)

249-249: LGTM!

The new rule option modules are correctly exported in alphabetical order.

Also applies to: 378-378

crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue (1)

1-13: LGTM!

The test fixture correctly demonstrates the valid pattern where v-if and v-for are on separate elements. This aligns well with the rule's intent.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue (1)

1-28: LGTM!

Excellent coverage of valid hyphenated attribute patterns across plain HTML, Vue directives, and custom components. The edge cases (dynamic arguments, non-v-bind directives) are particularly helpful.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue (1)

1-4: LGTM!

The fixture correctly validates that the ignore and ignore_tags options suppress diagnostics as expected.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue (1)

1-18: LGTM!

Good coverage of invalid patterns including camelCase, PascalCase, CONSTANT_CASE, and snake_case across plain HTML attributes and Vue directives.

crates/biome_rule_options/src/no_vue_v_if_with_v_for.rs (1)

1-6: LGTM!

The empty options struct is appropriate for a rule without configuration, and all the derives and serde attributes are correctly applied.

crates/biome_rule_options/src/use_vue_hyphenated_attributes.rs (1)

1-13: LGTM!

The options struct is well-designed with FxHashSet for efficient lookups and clear documentation for both configuration fields.

crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (3)

60-91: LGTM!

The run() method correctly identifies both v-if and v-for directives on the same element and captures their ranges. The logic is straightforward and handles the case where either directive is missing.


93-109: LGTM!

The diagnostic provides clear, actionable feedback with helpful suggestions for resolving the issue. The use of both the primary message and detail is well-structured.


45-45: No action required.

The rule name is correct. The ESLint Vue.js rule is vue/no-use-v-if-with-v-for, and the codebase correctly uses "no-use-v-if-with-v-for" as the suffix argument to RuleSource::EslintVueJs(). This matches the pattern used throughout the codebase for all Vue rules.

Likely an incorrect or invalid review comment.

crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (5)

13-56: LGTM!

The rule declaration is well-documented with clear examples. The Unsafe fix kind is appropriately marked with a comment indicating it's intentional whilst the team gains confidence in the rule.


64-90: LGTM!

The run() method correctly implements the ignore logic, checking tags first then individual attributes. The flow is clean and efficient.


92-108: LGTM!

Clear diagnostic message with helpful context from the Vue style guide.


110-181: LGTM!

The action method correctly handles token replacement for plain HTML attributes, Vue directives, and shorthand syntax. The batch mutation and trivia transfer are properly implemented.


227-230: LGTM!

The is_hyphenated() helper correctly uses Case::identify() with strict mode and appropriately accepts both kebab-case and pure lowercase attributes.

@mdevils
Copy link
Contributor

mdevils commented Nov 14, 2025

Great job!

@dyc3 dyc3 force-pushed the dyc3/vue-high-value-rules branch 2 times, most recently from 60c3473 to 794a504 Compare November 16, 2025 15:56
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: 0

🧹 Nitpick comments (3)
crates/biome_service/src/configuration.rs (1)

433-452: HTML rules and assists now participate in analyser configuration.

Adding html_lint_metadata to both push_to_analyzer_rules and push_to_analyzer_assist brings HTML/Vue rules into the same configuration pipeline as the other languages and allows their options to flow correctly through overrides.

If you ever add HTML rules with RuleDomain::Project, it may be worth also registering the HTML registry in ProjectScanComputer::compute so project scans account for them too; not urgent today as the current Vue rules are non‑project domain.

crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (1)

49-91: Implementation correctly detects elements combining v-if and v-for.

Scanning the HtmlAttributeList for Vue directives and tracking the v-if/v-for ranges is straightforward and robust; in practice there will only be one of each per element, so this works well.

You could shave a few cycles by breaking out of the loop once both v_if and v_for have been found, but that’s firmly in the micro‑optimisation bucket.

crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (1)

158-229: Fix logic covers the important Vue surface area without over‑reaching.

Handling plain HTML attributes, v-bind with static arguments, and v-bind shorthand in a single batch mutation keeps the implementation tidy while avoiding dynamic or non‑binding directives; the use of Case::Kebab.convert should give predictable rename suggestions.

If you ever need to touch more directive shapes, consider extracting the repeated “replace static argument token with suggested” snippet into a tiny helper to keep the action body from getting too branchy.

📜 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 60c3473 and 794a504.

⛔ Files ignored due to path filters (18)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_html_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-dynamic.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.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 (20)
  • .changeset/evil-experts-repeat.md (1 hunks)
  • .changeset/great-mammals-hide.md (1 hunks)
  • .changeset/loose-chairs-wonder.md (1 hunks)
  • crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (1 hunks)
  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (1 hunks)
  • crates/biome_html_analyze/tests/spec_tests.rs (3 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.options.json (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue (1 hunks)
  • crates/biome_html_parser/src/lexer/mod.rs (2 hunks)
  • crates/biome_html_parser/tests/quick_test.rs (1 hunks)
  • crates/biome_rule_options/src/lib.rs (2 hunks)
  • crates/biome_rule_options/src/no_vue_v_if_with_v_for.rs (1 hunks)
  • crates/biome_rule_options/src/use_vue_hyphenated_attributes.rs (1 hunks)
  • crates/biome_service/src/configuration.rs (2 hunks)
  • crates/biome_service/src/file_handlers/html.rs (3 hunks)
  • crates/biome_service/src/settings.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
  • crates/biome_html_parser/src/lexer/mod.rs
  • crates/biome_html_analyze/tests/spec_tests.rs
  • crates/biome_rule_options/src/no_vue_v_if_with_v_for.rs
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue
  • crates/biome_rule_options/src/use_vue_hyphenated_attributes.rs
  • crates/biome_html_parser/tests/quick_test.rs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-25T07:02:26.457Z
Learnt from: ematipico
Repo: biomejs/biome PR: 7856
File: .changeset/yellow-crews-guess.md:1-5
Timestamp: 2025-10-25T07:02:26.457Z
Learning: The Biome documentation website uses kebab-case URL slugs for rule pages (e.g., `/linter/rules/no-continue/`), not camelCase.

Applied to files:

  • .changeset/great-mammals-hide.md
  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
Repo: biomejs/biome PR: 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_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.options.json
🧬 Code graph analysis (4)
crates/biome_service/src/configuration.rs (2)
crates/biome_service/src/file_handlers/graphql.rs (8)
  • settings (308-308)
  • settings (312-312)
  • settings (316-316)
  • settings (353-353)
  • settings (369-369)
  • settings (387-387)
  • settings (401-401)
  • settings (500-500)
crates/biome_service/src/file_handlers/css.rs (8)
  • settings (383-383)
  • settings (387-387)
  • settings (391-391)
  • settings (456-456)
  • settings (478-478)
  • settings (496-496)
  • settings (510-510)
  • settings (544-544)
crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (1)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • domains (632-635)
  • sources (617-620)
  • same (246-251)
crates/biome_service/src/file_handlers/html.rs (2)
crates/biome_analyze/src/options.rs (1)
  • configuration (167-169)
crates/biome_service/src/configuration.rs (1)
  • to_analyzer_rules (434-452)
crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (2)
crates/biome_analyze/src/rule.rs (1)
  • same (246-251)
crates/biome_string_case/src/lib.rs (1)
  • identify (112-175)
🪛 LanguageTool
.changeset/loose-chairs-wonder.md

[style] ~4-~4: Consider using a different verb for a more formal wording.
Context: --- "@biomejs/biome": patch --- Fixed an issue with the HTML parser where it ...

(FIX_RESOLVE)

⏰ 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). (10)
  • GitHub Check: Test Node.js API
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: End-to-end tests
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Check JS Files
  • GitHub Check: autofix
  • GitHub Check: Bench (biome_configuration)
🔇 Additional comments (19)
.changeset/loose-chairs-wonder.md (1)

1-5: LGTM!

The changeset clearly documents the parser fix for Vue dynamic directive arguments.

crates/biome_service/src/settings.rs (2)

1403-1407: LGTM!

The HTML analyzer metadata integration for linter rules follows the established pattern used by other language analyzers.


1431-1435: LGTM!

The HTML analyzer metadata integration for assist actions is consistent with the existing structure.

.changeset/evil-experts-repeat.md (1)

1-12: LGTM!

The changeset clearly explains the new rule with a helpful example that illustrates the invalid pattern.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.options.json (1)

1-20: LGTM!

The test options configuration correctly specifies the ignore and ignoreTags settings for validating the rule's ignore functionality.

crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue (1)

1-13: LGTM!

The test fixture properly demonstrates the invalid patterns with v-if and v-for on the same element in both orderings.

.changeset/great-mammals-hide.md (1)

1-13: LGTM!

The changeset clearly documents the new rule with a concise example showing the camelCase → kebab-case transformation.

crates/biome_rule_options/src/lib.rs (2)

250-250: LGTM!

The no_vue_v_if_with_v_for module export is correctly positioned in alphabetical order.


380-380: LGTM!

The use_vue_hyphenated_attributes module export is correctly positioned in alphabetical order.

crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue (1)

1-13: LGTM!

The test fixture correctly demonstrates valid patterns where v-if and v-for are not combined on the same element.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue (1)

1-4: Fixture nicely exercises both ignore and ignoreTags.

The two lines cleanly cover the component/tag ignore and attribute-name ignore paths, matching the described options; nothing more to ask for here.

crates/biome_service/src/configuration.rs (1)

25-25: HTML lint metadata import is consistent with other languages.

Pulling in biome_html_analyze::METADATA under a dedicated alias mirrors the existing JS/CSS/JSON/GraphQL imports and keeps things uniform.

crates/biome_service/src/file_handlers/html.rs (1)

7-16: HTML analyser options now correctly carry rule configuration.

Using to_analyzer_rules(global, path.as_path()) to build an AnalyzerConfiguration and wiring it into AnalyzerOptions brings HTML/Vue in line with the other languages and allows rule options to be honoured; the signature change from _global to global is the only behavioural tweak and looks spot on.

Also applies to: 195-205, 208-210

crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (2)

9-47: Rule metadata and documentation align with the Vue style guide.

The rule name, domain (Vue), recommended status and ESLint Vue source all look correct, and the examples nicely mirror the common misuse patterns and fixes.


93-109: Diagnostic messaging is clear and points to concrete remedies.

Highlighting the v-for as the main span and using a .detail on the v-if to suggest moving it to a wrapper or using a computed property matches common Vue guidance and should be very understandable to users.

crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (4)

15-100: Rule definition and options match Vue’s attribute‑hyphenation expectations.

The rule metadata, examples and the ignore/ignoreTags options map neatly onto the existing Vue ecosystem, and marking the fix as Unsafe is a sensible bit of honesty given it might not always match component APIs.


102-138: Signal collection respects ignore options and focuses on true case violations.

Querying AnyTagWithAttributes, short‑circuiting on ignoreTags, then filtering attributes via extract_attribute_name + is_hyphenated gives you a tight signal set; the per‑attribute ignore check slots in cleanly and should be cheap in practice.


140-157: Diagnostics are concise and attribute‑centric.

Re‑deriving the attribute name for the message keeps things precise, and the note nudging users back towards the Vue style guide strikes a good balance between helpful and terse.


231-294: Helper utilities (AnyTagWithAttributes, extract_attribute_name, is_hyphenated) are well factored.

The small union for tags plus the focused extractors make the rule easier to read and reuse, and restricting is_hyphenated to Kebab | Lower via Case::identify does exactly what the docs promise.

@dyc3 dyc3 force-pushed the dyc3/vue-high-value-rules branch from 794a504 to 95ddb39 Compare November 16, 2025 19:18
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: 0

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

158-227: Action logic is sound; consider extracting the mutation helper.

The fix correctly handles plain HTML attributes and Vue directives (v-bind, v-model, shorthand), preserving trivia and skipping dynamic arguments. The duplicated token-replacement pattern (lines 172-178, 189-195, 205-210) could be extracted into a helper function to improve maintainability.

Example refactor for the mutation helper:

fn replace_name_token(
    mutation: &mut impl biome_rowan::BatchMutation,
    old_token: biome_html_syntax::HtmlSyntaxToken,
    new_name: &str,
) {
    let new_token = biome_html_syntax::HtmlSyntaxToken::new_detached(
        old_token.kind(),
        new_name,
        [],
        [],
    );
    mutation.replace_token_transfer_trivia(old_token, new_token);
}

Then use: replace_name_token(&mut mutation, old_token, &suggested);

📜 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 794a504 and 95ddb39.

⛔ Files ignored due to path filters (18)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_html_analyze/src/lint/nursery.rs is excluded by !**/nursery.rs and included by **
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-dynamic.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-bind-mixed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-dynamic-chains.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-else-if.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-html-text.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-mixed-complex.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-model-mixed.vue.snap is excluded by !**/*.snap and included by **
  • crates/biome_html_parser/tests/html_specs/ok/vue/v-on-mixed.vue.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 (20)
  • .changeset/evil-experts-repeat.md (1 hunks)
  • .changeset/great-mammals-hide.md (1 hunks)
  • .changeset/loose-chairs-wonder.md (1 hunks)
  • crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (1 hunks)
  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (1 hunks)
  • crates/biome_html_analyze/tests/spec_tests.rs (3 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.options.json (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue (1 hunks)
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue (1 hunks)
  • crates/biome_html_parser/src/lexer/mod.rs (2 hunks)
  • crates/biome_html_parser/tests/quick_test.rs (1 hunks)
  • crates/biome_rule_options/src/lib.rs (2 hunks)
  • crates/biome_rule_options/src/no_vue_v_if_with_v_for.rs (1 hunks)
  • crates/biome_rule_options/src/use_vue_hyphenated_attributes.rs (1 hunks)
  • crates/biome_service/src/configuration.rs (2 hunks)
  • crates/biome_service/src/file_handlers/html.rs (3 hunks)
  • crates/biome_service/src/settings.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • .changeset/evil-experts-repeat.md
  • crates/biome_service/src/settings.rs
  • crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/invalid.vue
  • crates/biome_service/src/file_handlers/html.rs
  • crates/biome_html_analyze/tests/spec_tests.rs
  • crates/biome_html_parser/src/lexer/mod.rs
  • crates/biome_rule_options/src/use_vue_hyphenated_attributes.rs
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/invalid.vue
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/valid.vue
  • crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.options.json
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-25T07:02:26.457Z
Learnt from: ematipico
Repo: biomejs/biome PR: 7856
File: .changeset/yellow-crews-guess.md:1-5
Timestamp: 2025-10-25T07:02:26.457Z
Learning: The Biome documentation website uses kebab-case URL slugs for rule pages (e.g., `/linter/rules/no-continue/`), not camelCase.

Applied to files:

  • crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs
  • .changeset/great-mammals-hide.md
📚 Learning: 2025-08-05T14:43:29.581Z
Learnt from: dyc3
Repo: biomejs/biome PR: 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:

  • .changeset/great-mammals-hide.md
🧬 Code graph analysis (4)
crates/biome_service/src/configuration.rs (2)
crates/biome_service/src/file_handlers/css.rs (9)
  • settings (383-383)
  • settings (387-387)
  • settings (391-391)
  • settings (456-456)
  • settings (478-478)
  • settings (496-496)
  • settings (510-510)
  • settings (544-544)
  • settings (614-614)
crates/biome_service/src/file_handlers/graphql.rs (7)
  • settings (308-308)
  • settings (312-312)
  • settings (316-316)
  • settings (353-353)
  • settings (369-369)
  • settings (387-387)
  • settings (401-401)
crates/biome_html_parser/tests/quick_test.rs (1)
crates/biome_html_parser/src/parser.rs (1)
  • options (32-34)
crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (2)
crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (3)
  • name (230-235)
  • run (112-138)
  • diagnostic (140-156)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • domains (632-635)
  • sources (617-620)
  • same (246-251)
crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (2)
crates/biome_analyze/src/rule.rs (4)
  • recommended (602-605)
  • domains (632-635)
  • sources (617-620)
  • same (246-251)
crates/biome_string_case/src/lib.rs (1)
  • identify (112-175)
🪛 LanguageTool
.changeset/loose-chairs-wonder.md

[style] ~4-~4: Consider using a different verb for a more formal wording.
Context: --- "@biomejs/biome": patch --- Fixed an issue with the HTML parser where it ...

(FIX_RESOLVE)

⏰ 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). (11)
  • GitHub Check: Check JS Files
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: End-to-end tests
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: autofix
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_configuration)
🔇 Additional comments (17)
.changeset/loose-chairs-wonder.md (1)

1-5: LGTM!

The changeset clearly documents the parser fix for Vue dynamic arguments. The phrasing is standard and appropriate for a patch-level change.

.changeset/great-mammals-hide.md (1)

1-13: LGTM!

The changeset is well-written and provides a clear example demonstrating the rule. The URL slug correctly follows the kebab-case convention.

crates/biome_html_parser/tests/quick_test.rs (1)

8-11: LGTM!

The test correctly enables Vue parsing mode to exercise dynamic argument syntax. The builder-style API usage is clean and idiomatic.

crates/biome_html_analyze/tests/specs/nursery/noVueVIfWithVFor/valid.vue (1)

1-13: LGTM!

The fixture correctly demonstrates the recommended pattern with v-if on the parent and v-for on the child, which should not trigger diagnostics.

crates/biome_html_analyze/tests/specs/nursery/useVueHyphenatedAttributes/ignore/valid-ignored.vue (1)

1-4: LGTM!

The fixture appropriately tests the ignore functionality, validating that configured exclusions prevent diagnostics from being generated.

crates/biome_service/src/configuration.rs (2)

25-25: LGTM!

The HTML lint metadata import follows the established pattern for other language analyzers.


434-452: LGTM!

The integration of HTML lint metadata into analyzer rules and assist actions is consistent with the existing CSS, JSON, and GraphQL handling. Well done!

crates/biome_rule_options/src/no_vue_v_if_with_v_for.rs (1)

1-6: LGTM!

The options struct follows the established pattern with appropriate derives and serde attributes. The empty body is fine for a rule with no configurable options (yet).

crates/biome_html_analyze/src/lint/nursery/no_vue_v_if_with_v_for.rs (3)

9-47: LGTM!

The rule declaration is comprehensive with clear documentation, examples, and proper metadata. The Vue style guide reference adds valuable context.


60-91: LGTM!

The detection logic correctly identifies both v-if and v-for directives on the same element. The state is only returned when both are present, which is exactly right.


93-109: LGTM!

The diagnostic provides clear messaging with helpful context. Highlighting the v-for range whilst adding detail for the v-if range effectively shows both problem locations.

crates/biome_html_analyze/src/lint/nursery/use_vue_hyphenated_attributes.rs (6)

1-14: LGTM!

Imports are clean and appropriate for the rule implementation.


15-100: Well-documented rule with sensible defaults.

The documentation is comprehensive, examples are clear, and marking the fix as Unsafe is appropriately conservative for automated attribute renaming.


229-243: LGTM!

Clean helper implementation for abstracting over opening and self-closing elements.


289-292: LGTM!

Correctly identifies kebab-case and pure lowercase as valid, using strict mode for accurate case detection.


112-138: Attribute comparison is correctly trimmed and consistent.

Verification confirms attr_name is already trimmed via token_text_trimmed() (line 251), and calling .text() on it returns that trimmed text—matching the pattern used elsewhere in the codebase. Both tag and attribute comparisons operate on trimmed text, so the ignore list matching behaves correctly regardless of user configuration.


245-287: Code is correct — no changes needed.

The parser properly separates directive names from modifiers. Line 258's name_token() returns just the directive name (e.g., "v-model"), whilst modifiers are accessed separately via .modifiers(). The filter correctly matches v-model and v-bind regardless of modifiers like .lazy or .number, so v-model.lazy will still be captured as expected.

@dyc3 dyc3 merged commit 5fc5416 into main Nov 16, 2025
17 checks passed
@dyc3 dyc3 deleted the dyc3/vue-high-value-rules branch November 16, 2025 20:52
@github-actions github-actions bot mentioned this pull request Nov 15, 2025
ematipico pushed a commit to hamirmahal/biome that referenced this pull request Nov 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Parser Area: parser A-Project Area: project D-Vue Domains: Vue L-HTML Language: HTML and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants