feat(lint/vue): implement useVueMultiWordComponentNames#7373
Conversation
|
WalkthroughRefactors Vue component representation into an enum Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested labels
Suggested reviewers
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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). (26)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
Parser conformance results onjs/262
jsx/babel
symbols/microsoft
ts/babel
ts/microsoft
|
CodSpeed Performance ReportMerging #7373 will not alter performanceComparing Summary
|
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (8)
crates/biome_rowan/src/token_text.rs (1)
60-63: Clarify that the returned range is relative; add inline hint.Small doc tweak for accuracy and a tiny nudge to the optimiser.
- /// Returns the range of the text - pub fn range(&self) -> TextRange { + /// Returns the relative range of the selected text within this token + #[inline] + pub fn range(&self) -> TextRange { self.range }crates/biome_rule_options/src/use_vue_multi_word_component_names.rs (1)
7-12: Expose default in generated schema.So downstream tooling sees the default
["App"], add schemars default on the field./// Defaults to ["App"], mirroring the common root component naming convention. - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(skip_serializing_if = "Vec::is_empty")] + #[cfg_attr(feature = "schema", schemars(default = "default_ignores"))] pub ignores: Vec<String>,crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue (1)
1-4: Nice negative case; add one for the default ignore.Consider adding
App.vueto assert the built‑in ignore works as expected.Example:
<!-- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/App.vue --> <script> export default {} </script>crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue (1)
1-5: LGTM: kebab-case FromPath happy-pathOptional: add an App.vue fixture to assert the default ignore works end-to-end.
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js (1)
1-22: Solid coverage of “no diagnostic” cases.Optional: add KeepAlive and Teleport to exercise more built-in exemptions.
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (1)
248-258: Minor: this assertion passes due to multi-word, not the built-in list.
"transition-group"is multi-word, so it wouldn’t be reported regardless of builtin ignores. Consider adding a single-word built-in (e.g."Transition","Teleport") here for a stronger signal.crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (2)
23-26: Module split and re-export look tidyIf
VueComponentNameisn’t needed outside this crate, considerpub(crate)to keep the public surface lean.
65-69: Consider deriving Debug for VueComponentHandy for diagnostics and tests; no behavioural change.
Apply:
+#[derive(Debug)] pub struct VueComponent<'a> { kind: AnyVueComponent, path: &'a Utf8Path, }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (11)
crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/src/lint/nursery.rsis excluded by!**/nursery.rsand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (15)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs(4 hunks)crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js(1 hunks)crates/biome_rowan/src/token_text.rs(1 hunks)crates/biome_rule_options/src/lib.rs(1 hunks)crates/biome_rule_options/src/use_vue_multi_word_component_names.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
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/useVueMultiWordComponentNames/invalid.jscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.jscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vuecrates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vuecrates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.jscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.jscrates/biome_rowan/src/token_text.rscrates/biome_rule_options/src/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vuecrates/biome_rule_options/src/lib.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vuecrates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vuecrates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.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/useVueMultiWordComponentNames/invalid.jscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.jscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (use
just f/just format).
Files:
crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rscrates/biome_rowan/src/token_text.rscrates/biome_rule_options/src/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rscrates/biome_rule_options/src/lib.rscrates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rscrates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.rs
🧠 Learnings (7)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/tests/specs/**/{invalid*,valid*}.{js,jsx,ts,tsx,css,graphql,jsonc} : Place snapshot test cases under `tests/specs/<group>/<ruleName>/` using files prefixed with `invalid` and `valid`
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.jscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.jscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vuecrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Define per-rule options in the `biome_rule_options` crate under `lib/`, with serde- and schemars-compatible derives and `#[serde(rename_all = "camelCase", deny_unknown_fields, default)]`
Applied to files:
crates/biome_rule_options/src/use_vue_multi_word_component_names.rscrates/biome_rule_options/src/lib.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : For new JavaScript lint rules generated by `just new-js-lintrule`, implement the rule in the generated file under `biome_js_analyze/lib/src/lint/nursery/`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : In `declare_lint_rule!` declarations, set `version: "next"`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use `domains` in `declare_lint_rule!` when applicable; recommended rules with domains enable only when the domain is active
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : If a rule provides a code action, add `fix_kind` in `declare_lint_rule!` and use `ctx.action_category(ctx.category(), ctx.group())` and `ctx.metadata().applicability()` when constructing actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : When porting from other linters, declare `sources: &[RuleSource::<...>]` in `declare_lint_rule!` using `.same()` or `.inspired()` as appropriate
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
🧬 Code graph analysis (5)
crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs (1)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (2)
from_potential_component(79-91)from_potential_component(132-185)
crates/biome_rowan/src/token_text.rs (3)
crates/biome_js_analyze/src/lint/nursery/no_unresolved_imports.rs (1)
range(75-80)crates/biome_deserialize/src/json.rs (2)
range(83-85)range(238-240)crates/biome_module_graph/src/js_module_info/scope.rs (1)
range(408-410)
crates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rs (1)
crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs (1)
ctx(121-121)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (4)
crates/biome_html_syntax/src/file_source.rs (1)
vue(85-89)crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (4)
name(96-102)from_potential_component(79-91)from_potential_component(132-185)new(71-73)crates/biome_rowan/src/token_text.rs (2)
range(61-63)new(40-43)crates/biome_rule_options/src/use_vue_multi_word_component_names.rs (1)
default(19-23)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (2)
crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs (1)
component(127-145)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs (1)
component(127-138)
⏰ 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). (25)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Check Dependencies
- GitHub Check: End-to-end tests
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Documentation
- GitHub Check: Test Node.js API
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Check JS Files
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: autofix
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Parser conformance
🔇 Additional comments (14)
crates/biome_rule_options/src/use_vue_multi_word_component_names.rs (1)
18-24: Default + serde(default) interplay – confirm intent.With struct-level
#[serde(default)]and thisDefaultimpl, deserialising withoutignoresyields["App"]. That also meansskip_serializing_if = "Vec::is_empty"won’t omit the field when serialising defaults. If you prefer minimal serialised configs, consider keeping the rule’s built-in default in the analyser and leaving the option default empty. Otherwise, all good—just double‑check this matches Biome’s conventions.Would you like me to add a tiny deserialisation round‑trip test to lock this behaviour in?
crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs (1)
122-123: API usage is consistent with the new signature.Passing
ctx.file_path()as the fourth argument matches the updatedVueComponent::from_potential_componentAPI. Ship it.crates/biome_rule_options/src/lib.rs (1)
356-356: Options module matches conventions
All checks passed:UseVueMultiWordComponentNamesOptionsis present, derives includeSerialize, serde attrs (rename_all = "camelCase",deny_unknown_fields,default) are correct, anddefault_ignoresincludes"App". Codegen should pick it up.crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue (1)
1-5: LGTM: verifies PascalCase filename inferenceGood coverage for FromPath + PascalCase treated as multi-word.
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue (1)
1-5: Solid negative case; consider asserting highlight spansNice explicit single-word name. If snapshots support it, assert the diagnostic points at the "invalid" token (now that TokenText::range is exposed).
crates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rs (1)
123-128: Good call passing the file path into VueComponent::from_potential_component.This aligns the call-site with the path-aware component model and keeps name inference consistent across rules.
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (2)
235-246: Update the test to reflect the improved acronym handling.With the revised heuristic,
MYComponentis correctly treated as multi-word.- assert!(!is_multi_word("MYComponent")); // Treated as single segment (no lower->upper boundary until after first lowercase) + assert!(is_multi_word("MYComponent")); // Acronym + word is multi-word
117-123: Remove TokenText::range() check
TokenText::range()computes its span by adding the stored offset totoken.text_range(), yielding a source-absoluteTextRange, so no change is needed.crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (6)
17-17: Good call using Utf8PathAvoids lossy conversions and matches the repo’s cross‑platform path story.
70-78: LGTM on ctor/accessorStraightforward and readable.
107-129: Enum rename to AnyVueComponent reads clearerNice separation between “wrapper with path” and the inner “any kind” enum.
131-186: Component detection logic looks soundConservative checks, and
definePropsgated by Vue embedding is a good touch. Clones are cheap on rowan nodes, so fine here.
255-266: Delegation impl is cleanKeeps
VueComponentthin; zero behavioural drift.
268-289: Dispatch over variants: steadyMatches prior pattern; nothing alarming.
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
Show resolved
Hide resolved
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js
Outdated
Show resolved
Hide resolved
6dc4b13 to
b483760
Compare
3097e2f to
c032373
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
crates/biome_rule_options/src/use_vue_multi_word_component_names.rs (1)
7-9: Tighten the doc comment for clarity (tiny nit)Clarify matching semantics to pre-empt user confusion.
Apply this doc-only tweak:
- /// Component names to ignore (allowed to be single-word). + /// Component names to ignore (allowed to be single-word names). + /// Matches exact component names as derived by the rule (not glob/pattern).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (12)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/src/lint/nursery.rsis excluded by!**/nursery.rsand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (15)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs(4 hunks)crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js(1 hunks)crates/biome_rowan/src/token_text.rs(1 hunks)crates/biome_rule_options/src/lib.rs(1 hunks)crates/biome_rule_options/src/use_vue_multi_word_component_names.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue
- crates/biome_rowan/src/token_text.rs
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue
- crates/biome_rule_options/src/lib.rs
- crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs
- crates/biome_js_analyze/src/lint/nursery/no_vue_data_object_declaration.rs
- crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs
- crates/biome_js_analyze/src/frameworks/vue/vue_component.rs
- crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (use
just f/just format).
Files:
crates/biome_rule_options/src/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_rule_options/src/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.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/src/lint/nursery/no_vue_reserved_props.rs
🧠 Learnings (1)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Define per-rule options in the `biome_rule_options` crate under `lib/`, with serde- and schemars-compatible derives and `#[serde(rename_all = "camelCase", deny_unknown_fields, default)]`
Applied to files:
crates/biome_rule_options/src/use_vue_multi_word_component_names.rs
🔇 Additional comments (2)
crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs (1)
118-123: Passingctx.file_path()into the component resolver — LGTMThis unblocks file-name–based name derivation and aligns this rule with the new Vue component APIs. Nicely plumbed.
crates/biome_rule_options/src/use_vue_multi_word_component_names.rs (1)
1-10: Options struct looks correct and matches project conventionsDerives, serde config (
camelCase,deny_unknown_fields,default), and visibility are spot on. Good to ship.
This comment was marked as resolved.
This comment was marked as resolved.
fafa668 to
03c5c85
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)
519-529: Methods are being classified as Computed.
collect_computed_and_method_declarationsalways wraps asComputed, somethodsend up mislabelled. This can break filters and downstream rules.Apply:
- fn collect_computed_and_method_declarations(expression: &AnyJsExpression) -> Vec<VueDeclaration> { + fn collect_object_member_methods( + expression: &AnyJsExpression, + wrap: fn(AnyVueMethod) -> VueDeclaration, + ) -> Vec<VueDeclaration> { @@ - .filter_map(|member| match member { - AnyJsObjectMember::JsPropertyObjectMember(property) => Some(VueDeclaration::Computed( - AnyVueMethod::JsPropertyObjectMember(property), - )), - AnyJsObjectMember::JsMethodObjectMember(method) => Some(VueDeclaration::Computed( - AnyVueMethod::JsMethodObjectMember(method), - )), + .filter_map(|member| match member { + AnyJsObjectMember::JsPropertyObjectMember(property) => { + Some(wrap(AnyVueMethod::JsPropertyObjectMember(property))) + } + AnyJsObjectMember::JsMethodObjectMember(method) => { + Some(wrap(AnyVueMethod::JsMethodObjectMember(method))) + } _ => None, })And update call sites:
@@ - if let Ok(expression) = property.value() { - result.extend(collect_computed_and_method_declarations(&expression)); + if let Ok(expression) = property.value() { + result.extend( + collect_object_member_methods(&expression, VueDeclaration::Computed) + ); } @@ - if let Ok(expression) = property.value() { - result.extend(collect_computed_and_method_declarations(&expression)); + if let Ok(expression) = property.value() { + result.extend( + collect_object_member_methods(&expression, VueDeclaration::Method) + ); }Also applies to: 531-541, 910-928
♻️ Duplicate comments (1)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)
90-105: Tests for name precedence and JS/TS fallback (dup of earlier request).If not already in this PR, please add/confirm tests: explicit name > component def > SFC filename; and no filename fallback for .js/.ts.
🧹 Nitpick comments (4)
crates/biome_rowan/src/token_text.rs (1)
60-64: Clarify that this range is token-relative (avoid confusion with absolute ranges)Across the codebase,
range()often means an absolute document range. A tiny doc tweak here will save future head‑scratching.Apply this diff to the doc comment:
- /// Returns the range of the text + /// Returns the relative range within the underlying token's text. + /// + /// Note: This is not an absolute document range. To obtain an absolute range, + /// offset this by the containing `SyntaxToken`'s `text_range().start()`.crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.json (2)
10-13: Add a kebab-case ignore test for completenessIf the rule normalises names, consider a case using
foo.vueorname: 'foo'to confirm"Foo"inignoresalso covers kebab/file-derived names (or explicitly doesn’t).Happy to draft an extra fixture (valid/invalid) if you confirm expected behaviour.
3-5: Minor:linter.enabledmay be redundant in specsIf the test harness enables linting by default, this field can be omitted to keep the spec minimal. No need to change if you prefer explicitness.
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)
90-105: Make the .vue extension check case-insensitive (tiny nit).A small guard against odd casing on some filesystems.
Apply:
- if self.path.extension() == Some("vue") { + if self + .path + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("vue")) + {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (13)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/src/lint/nursery.rsis excluded by!**/nursery.rsand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/valid-ignored.js.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (13)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs(4 hunks)crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.json(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/valid-ignored.js(1 hunks)crates/biome_rowan/src/token_text.rs(1 hunks)crates/biome_rule_options/src/lib.rs(1 hunks)crates/biome_rule_options/src/use_vue_multi_word_component_names.rs(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/valid-ignored.js
🚧 Files skipped from review as they are similar to previous changes (9)
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js
- crates/biome_rule_options/src/lib.rs
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue
- crates/biome_rule_options/src/use_vue_multi_word_component_names.rs
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue
- crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (use
just f/just format).
Files:
crates/biome_rowan/src/token_text.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_rowan/src/token_text.rscrates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.jsoncrates/biome_js_analyze/src/frameworks/vue/vue_component.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/useVueMultiWordComponentNames/with-ignore/options.jsoncrates/biome_js_analyze/src/frameworks/vue/vue_component.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/useVueMultiWordComponentNames/with-ignore/options.json
🧠 Learnings (3)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : For new JavaScript lint rules generated by `just new-js-lintrule`, implement the rule in the generated file under `biome_js_analyze/lib/src/lint/nursery/`
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.json
📚 Learning: 2025-08-11T11:48:27.774Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-08-11T11:48:27.774Z
Learning: Applies to crates/biome_formatter/biome_html_formatter/tests/specs/html/**/options.json : When non-default formatting options are needed for a test group, place an options.json (biome.json format) alongside the .html files in that folder
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/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/useVueMultiWordComponentNames/with-ignore/options.json
🧬 Code graph analysis (2)
crates/biome_rowan/src/token_text.rs (3)
crates/biome_js_analyze/src/lint/nursery/no_unresolved_imports.rs (1)
range(75-80)crates/biome_deserialize/src/json.rs (2)
range(83-85)range(238-240)crates/biome_module_graph/src/js_module_info/scope.rs (1)
range(408-410)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (3)
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (1)
component_name(7-49)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs (1)
component(127-138)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs (1)
component(127-145)
⏰ 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). (26)
- GitHub Check: Check JS Files
- GitHub Check: Documentation
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_package)
- GitHub Check: Check Dependencies
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: Test Node.js API
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: autofix
- GitHub Check: Parser conformance
🔇 Additional comments (6)
crates/biome_rowan/src/token_text.rs (1)
60-64: LGTM: exposingrange()unblocks precise diagnosticsThis is a handy accessor and aligns with how
TokenTextis already used downstream.crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.json (2)
7-14: Rule wiring and options look correctKey name, level, and
ignoresstructure align with the rule intent. Nice.
2-2: Ensure schema generation before tests
The$schemapath correctly climbs seven levels topackages/@biomejs/biome/configuration_schema.json. That file is auto-generated (and so isn’t in source), not a path typo. No change needed here—just confirm your build/test pipeline producesconfiguration_schema.jsonprior to running these specs.crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (3)
23-26: Nice split and re-export.Good move isolating
component_nameand re-exportingVueComponentName.
65-88: Wrapper looks solid.Carrying
pathalongsideAnyVueComponentsimplifies name resolution and keeps call-sites tidy.
134-189: Factory refactor reads well.Centralising detection in
AnyVueComponent::from_potential_componentis cleaner and reduces duplication at call sites.
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
Show resolved
Hide resolved
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
Show resolved
Hide resolved
...biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue.snap
Outdated
Show resolved
Hide resolved
|
Approved it, but the docs must be addressed |
2f7eed3 to
32a186b
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (2)
518-541: Bug: methods are emitted as ComputedIn both “computed” and “methods” branches you call the same collector, which returns
VueDeclaration::Computedfor everything. Methods end up misclassified.Apply this diff to split collectors and tag correctly:
- "computed" => { + "computed" => { if !filter.contains(VueDeclarationCollectionFilter::Computed) { continue; } let AnyJsObjectMember::JsPropertyObjectMember(property) = group_object_member else { continue; }; if let Ok(expression) = property.value() { - result.extend(collect_computed_and_method_declarations(&expression)); + result.extend(collect_computed_declarations(&expression)); } } "methods" => { if !filter.contains(VueDeclarationCollectionFilter::Method) { continue; } let AnyJsObjectMember::JsPropertyObjectMember(property) = group_object_member else { continue; }; if let Ok(expression) = property.value() { - result.extend(collect_computed_and_method_declarations(&expression)); + result.extend(collect_method_declarations(&expression)); } }
910-928: Follow-up for the misclassification: split the collectorReplace the shared collector with two dedicated helpers.
-fn collect_computed_and_method_declarations(expression: &AnyJsExpression) -> Vec<VueDeclaration> { +fn collect_computed_declarations(expression: &AnyJsExpression) -> Vec<VueDeclaration> { let AnyJsExpression::JsObjectExpression(object_expression) = expression else { return vec![]; }; object_expression .members() .iter() .flatten() .filter_map(|member| match member { - AnyJsObjectMember::JsPropertyObjectMember(property) => Some(VueDeclaration::Computed( - AnyVueMethod::JsPropertyObjectMember(property), - )), - AnyJsObjectMember::JsMethodObjectMember(method) => Some(VueDeclaration::Computed( - AnyVueMethod::JsMethodObjectMember(method), - )), + AnyJsObjectMember::JsPropertyObjectMember(property) => { + Some(VueDeclaration::Computed(AnyVueMethod::JsPropertyObjectMember(property))) + } + AnyJsObjectMember::JsMethodObjectMember(method) => { + Some(VueDeclaration::Computed(AnyVueMethod::JsMethodObjectMember(method))) + } _ => None, }) .collect() } + +fn collect_method_declarations(expression: &AnyJsExpression) -> Vec<VueDeclaration> { + let AnyJsExpression::JsObjectExpression(object_expression) = expression else { + return vec![]; + }; + object_expression + .members() + .iter() + .flatten() + .filter_map(|member| match member { + AnyJsObjectMember::JsPropertyObjectMember(property) => { + Some(VueDeclaration::Method(AnyVueMethod::JsPropertyObjectMember(property))) + } + AnyJsObjectMember::JsMethodObjectMember(method) => { + Some(VueDeclaration::Method(AnyVueMethod::JsMethodObjectMember(method))) + } + _ => None, + }) + .collect() +}
♻️ Duplicate comments (5)
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (1)
27-37: Good fix: only literalnamekeys are inspectedThe guard now correctly skips computed keys and non-
namemembers. This addresses the earlier concern.crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)
93-105: SFC-only filename fallback looks rightNice gating of filename fallback to
.vue. Prevents JS/TS files from being misnamed.crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (3)
219-286: Reuse the normaliser inis_multi_wordfor simpler, more robust logic.This covers acronym→word splits and keeps underscore/hyphen edge cases consistent with tests.
-fn is_multi_word(name: &str) -> bool { - let mut segments = 0u8; - let mut chars = name.chars().peekable(); - - while let Some(ch) = chars.next() { - match ch { - '-' | '_' => { - // Explicit separators don't count as segments themselves - - // because there is a separator, we know there is a segment after this - // no need to keep going - return true; - } - _ => { - // Start a new segment - segments += 1; - if segments > 1 { - return true; - } - - // Skip to the end of this segment - let mut prev_was_upper = ch.is_ascii_uppercase(); - while let Some(&next_ch) = chars.peek() { - match next_ch { - '-' | '_' => { - // End of segment due to separator - break; - } - c if c.is_ascii_uppercase() => { - if !prev_was_upper { - // lowercase/digit -> uppercase: start new segment - break; - } - // Check if this uppercase is followed by lowercase (like 'B' in "UIButton") - chars.next(); // consume this uppercase char - if let Some(&after_upper) = chars.peek() - && after_upper.is_ascii_lowercase() - && prev_was_upper - { - // This uppercase starts a new word (UI|Button pattern) - segments += 1; - if segments > 1 { - return true; - } - prev_was_upper = true; - continue; - } - prev_was_upper = true; - } - _ => { - // lowercase or digit - continue in same segment - chars.next(); - prev_was_upper = false; - } - } - } - } - } - if segments > 1 { - return true; - } - } - false -} +fn is_multi_word(name: &str) -> bool { + // After normalisation, multi-word iff there is at least one hyphen. + // Note: we intentionally do not trim leading/trailing hyphens so inputs like "_Foo" / "Foo_" still count, + // matching the current tests. + normalise_name(name).contains('-') +}
198-217: Fix ignores: normalise kebab/Pascal/snake before comparing (current tests will fail).
eq_ignore_ascii_casewon’t match "FooBar" with "foo-bar". Canonicalise both sides first, then compare. This also future‑proofs acronym/underscore variants.Apply this diff (adds a small normaliser and uses it in the comparisons):
@@ -/// Determines if the given component name should be reported (i.e. is invalid single-word). -fn should_report(name: &str, options: &UseVueMultiWordComponentNamesOptions) -> bool { +fn normalise_name(name: &str) -> String { + // Convert Camel/PascalCase and underscores to a kebab-like lowercase form. + let bytes = name.as_bytes(); + let mut out = String::with_capacity(bytes.len() + 4); + for i in 0..bytes.len() { + let b = bytes[i]; + match b { + b'-' | b'_' => { + if !out.ends_with('-') { + out.push('-'); + } + } + b'A'..=b'Z' => { + let prev = if i > 0 { Some(bytes[i - 1]) } else { None }; + let next = if i + 1 < bytes.len() { Some(bytes[i + 1]) } else { None }; + let prev_is_lower_or_digit = + prev.map(|p| p.is_ascii_lowercase() || p.is_ascii_digit()).unwrap_or(false); + let prev_is_upper = prev.map(|p| p.is_ascii_uppercase()).unwrap_or(false); + let next_is_lower = next.map(|n| n.is_ascii_lowercase()).unwrap_or(false); + if !out.is_empty() && (prev_is_lower_or_digit || (prev_is_upper && next_is_lower)) && !out.ends_with('-') { + out.push('-'); + } + out.push((b as char).to_ascii_lowercase()); + } + other => out.push((other as char).to_ascii_lowercase()), + } + } + out +} + +/// Determines if the given component name should be reported (i.e. is invalid single-word). +fn should_report(name: &str, options: &UseVueMultiWordComponentNamesOptions) -> bool { if name.is_empty() { return true; // invalid, not covered by ignores } - // We could binary search, but the list is so short that linear scan is probably faster - if BUILTIN_IGNORES.iter().any(|s| s.eq_ignore_ascii_case(name)) { + // We could binary search, but the list is so short that linear scan is probably faster + let canon = normalise_name(name); + if BUILTIN_IGNORES.iter().any(|s| normalise_name(s) == canon) { return false; } for user in &options.ignores { - if name.eq_ignore_ascii_case(user) { + if normalise_name(user) == canon { return false; } } // Report if NOT multi-word !is_multi_word(name) }
19-24: Doc precision: describe the actual heuristic (separators + case boundaries) and mention diagnostic note.The “two capitals” rule misleads for
myComponent. Let’s reflect the implemented behaviour.- /// A name is considered multi-word when: - /// - Kebab-case: contains at least one hyphen (`my-component`) - /// - PascalCase / CamelCase: contains at least two capital letters (`MyComponent`); single-cap names like `App` or `Foo` are rejected + /// A name is considered multi-word when: + /// - Kebab-/snake-case: contains at least one separator (`my-component`, `my_component`) + /// - PascalCase/CamelCase: a case boundary or acronym→word transition exists (e.g. `MyComponent`, `myComponent`, `MYComponent`) @@ - /// Component names are extracted from the `name` property in Options API components, or inferred from the file name if not explicitly set. + /// Component names are extracted from the `name` property in Options API components, or inferred from the file name if not explicitly set. Diagnostics indicate when the name was inferred.
🧹 Nitpick comments (6)
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (1)
39-49: Handle static template literals fornameConsider supporting backticked, no-substitution names (e.g. name:
MyComp) alongside string literals, so we catch more valid cases. Safe to treat template-without-expressions as a string.crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue (1)
1-5: Test case reads clearlyGood negative fixture with explicit
name. If possible, assert the diagnostic range points to the string literal to keep UX crisp.crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (4)
61-71: Docs: add a CamelCase example (myComponent) to the Valid section.Keeps examples aligned with the heuristic.
/// ```js /// export default { /// name: "my-component" /// }; /// ``` + /// + /// ```js + /// export default { + /// name: "myComponent" + /// }; + /// ```
81-82: Option docs: clarify matching is case- and style-insensitive.Ignores will match Pascal/kebab/snake variants after your normalisation.
- /// Additional single-word component names to ignore (case-insensitive). The rule already ignores Vue built-in components and `App` by default. + /// Additional single-word component names to ignore. Matching is case- and style-insensitive (PascalCase / kebab-case / snake_case). The rule already ignores Vue built-in components and `App` by default.
165-173: Diagnostic copy: small grammar/polish tweak.Lower-case “component” and “contains only” reads better; “two or more” instead of “2 or more”.
- "This Component's name "<Emphasis>"\""{component_name}"\""</Emphasis>" only contains one word." + "This component's name "<Emphasis>"\""{component_name}"\""</Emphasis>" contains only one word." @@ - "Rename the component to have 2 or more words (e.g. \"FooItem\", or \"BarView\")." + "Rename the component to use two or more words (e.g. \"FooItem\" or \"BarView\")."
288-350: Nice test coverage; add one for snake_case ignore to lock in normalisation.Optional but cheap win.
#[test] fn test_should_report_user_ignores_snake_case() { let mut options = UseVueMultiWordComponentNamesOptions::default(); options.ignores.push("foo_bar".to_string()); // snake_case assert!(!should_report("FooBar", &options)); // PascalCase variant assert!(!should_report("foo-bar", &options)); // kebab-case variant assert!(!should_report("foo_bar", &options)); // snake_case itself }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (13)
crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rsis excluded by!**/migrate/eslint_any_rule_to_biome.rsand included by**crates/biome_configuration/src/analyzer/linter/rules.rsis excluded by!**/rules.rsand included by**crates/biome_diagnostics_categories/src/categories.rsis excluded by!**/categories.rsand included by**crates/biome_js_analyze/src/lint/nursery.rsis excluded by!**/nursery.rsand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/valid-ignored.js.snapis excluded by!**/*.snapand included by**packages/@biomejs/backend-jsonrpc/src/workspace.tsis excluded by!**/backend-jsonrpc/src/workspace.tsand included by**packages/@biomejs/biome/configuration_schema.jsonis excluded by!**/configuration_schema.jsonand included by**
📒 Files selected for processing (13)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs(4 hunks)crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.json(1 hunks)crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/valid-ignored.js(1 hunks)crates/biome_rule_options/src/lib.rs(1 hunks)crates/biome_rule_options/src/use_vue_multi_word_component_names.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/ValidPascalCase.vue
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid-kebab-case.vue
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/valid.js
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/valid-ignored.js
- crates/biome_rule_options/src/lib.rs
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.vue
- crates/biome_rule_options/src/use_vue_multi_word_component_names.rs
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js
- crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/with-ignore/options.json
🧰 Additional context used
📓 Path-based instructions (4)
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/useVueMultiWordComponentNames/invalid-has-name.vuecrates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vuecrates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.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/useVueMultiWordComponentNames/invalid-has-name.vue
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (use
just f/just format).
Files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rscrates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rscrates/biome_js_analyze/src/frameworks/vue/vue_component.rs
🧠 Learnings (8)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/tests/specs/**/{invalid*,valid*}.{js,jsx,ts,tsx,css,graphql,jsonc} : Place snapshot test cases under `tests/specs/<group>/<ruleName>/` using files prefixed with `invalid` and `valid`
Applied to files:
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : For new JavaScript lint rules generated by `just new-js-lintrule`, implement the rule in the generated file under `biome_js_analyze/lib/src/lint/nursery/`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : In `declare_lint_rule!` declarations, set `version: "next"`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : When porting from other linters, declare `sources: &[RuleSource::<...>]` in `declare_lint_rule!` using `.same()` or `.inspired()` as appropriate
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use `domains` in `declare_lint_rule!` when applicable; recommended rules with domains enable only when the domain is active
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : If a rule provides a code action, add `fix_kind` in `declare_lint_rule!` and use `ctx.action_category(ctx.category(), ctx.group())` and `ctx.metadata().applicability()` when constructing actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Place all new rules in the nursery group (implement new rule files under a `nursery` directory)
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : If a rule is deprecated, set the `deprecated:` field in `declare_lint_rule!` with a reason and alternative
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
🧬 Code graph analysis (3)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (3)
crates/biome_html_syntax/src/file_source.rs (1)
vue(85-89)crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (4)
name(93-105)from_potential_component(79-88)from_potential_component(135-188)new(71-73)crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (1)
component_name(9-53)
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (2)
crates/biome_rowan/src/token_text.rs (7)
eq(98-100)eq(104-106)eq(110-112)partial_cmp(29-31)cmp(23-25)deref(80-82)as_ref(116-118)crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (1)
name(93-105)
crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (3)
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (1)
component_name(9-53)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_props.rs (1)
component(127-138)crates/biome_js_analyze/src/lint/nursery/no_vue_reserved_keys.rs (1)
component(127-145)
⏰ 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). (22)
- GitHub Check: Check JS Files
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Test Node.js API
- GitHub Check: Documentation
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: autofix
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
🔇 Additional comments (2)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (2)
107-114: Rule declaration looks solid.Version, domain, and source attribution are correct for a nursery rule; recommending it under the Vue domain makes sense.
123-142: Signal/state handling LGTM.Clean separation between precise token vs file-stem fallback; good use of
ctx.file_path().
32a186b to
0213bfa
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (3)
19-24: Docs: clarify the heuristic (case boundaries, underscores) and keep it aligned with the implementation.The current “two capitals” description is inaccurate and omits underscores. Please describe separators and case-boundary splitting.
Apply this diff:
- /// A name is considered multi-word when: - /// - Kebab-case: contains at least one hyphen (`my-component`) - /// - PascalCase / CamelCase: contains at least two capital letters (`MyComponent`); single-cap names like `App` or `Foo` are rejected + /// A name is considered multi-word when: + /// - It contains an explicit separator: hyphen (`my-component`) or underscore (`my_component`), or + /// - It contains a case boundary: transitions from lowercase/digit to uppercase, and acronym→word splits (e.g. `UI` + `Button` in `UIButton`).
198-217: Ignores should match kebab/Pascal variants via normalisation.
eq_ignore_ascii_casewon’t match"FooBar"vs"foo-bar". Normalise both sides before comparing.Apply this diff:
+fn normalise_name(name: &str) -> String { + let bytes = name.as_bytes(); + let mut out = String::with_capacity(bytes.len() + 4); + for i in 0..bytes.len() { + let b = bytes[i]; + match b { + b'-' | b'_' => { + if !out.ends_with('-') { out.push('-'); } + } + b'A'..=b'Z' => { + let prev = i.checked_sub(1).map(|j| bytes[j]); + let next = (i + 1 < bytes.len()).then_some(bytes[i + 1]); + let prev_is_lower_or_digit = prev.map(|p| p.is_ascii_lowercase() || p.is_ascii_digit()).unwrap_or(false); + let prev_is_upper = prev.map(|p| p.is_ascii_uppercase()).unwrap_or(false); + let next_is_lower = next.map(|n| n.is_ascii_lowercase()).unwrap_or(false); + if !out.is_empty() && (prev_is_lower_or_digit || (prev_is_upper && next_is_lower)) && !out.ends_with('-') { + out.push('-'); + } + out.push((b as char).to_ascii_lowercase()); + } + other => out.push((other as char).to_ascii_lowercase()), + } + } + out +} + fn should_report(name: &str, options: &UseVueMultiWordComponentNamesOptions) -> bool { if name.is_empty() { return true; // invalid, not covered by ignores } - // We could binary search, but the list is so short that linear scan is probably faster - if BUILTIN_IGNORES.iter().any(|s| s.eq_ignore_ascii_case(name)) { + // We could binary search, but the list is tiny; linear is fine. + let canon = normalise_name(name); + if BUILTIN_IGNORES.iter().any(|s| normalise_name(s) == canon) { return false; } - for user in &options.ignores { - if name.eq_ignore_ascii_case(user) { + for user in &options.ignores { + if normalise_name(user) == canon { return false; } } // Report if NOT multi-word !is_multi_word(name) }
219-286: Simplify multi-word detection by reusing the same normalisation logic.This avoids edge-case drift (e.g. acronym→word splits) and keeps ignores and detection consistent.
Apply this diff:
-fn is_multi_word(name: &str) -> bool { - let mut segments = 0u8; - let mut chars = name.chars().peekable(); - - while let Some(ch) = chars.next() { - match ch { - '-' | '_' => { - // Explicit separators don't count as segments themselves - - // because there is a separator, we know there is a segment after this - // no need to keep going - return true; - } - _ => { - // Start a new segment - segments += 1; - if segments > 1 { - return true; - } - - // Skip to the end of this segment - let mut prev_was_upper = ch.is_ascii_uppercase(); - while let Some(&next_ch) = chars.peek() { - match next_ch { - '-' | '_' => { - // End of segment due to separator - break; - } - c if c.is_ascii_uppercase() => { - if !prev_was_upper { - // lowercase/digit -> uppercase: start new segment - break; - } - // Check if this uppercase is followed by lowercase (like 'B' in "UIButton") - chars.next(); // consume this uppercase char - if let Some(&after_upper) = chars.peek() - && after_upper.is_ascii_lowercase() - && prev_was_upper - { - // This uppercase starts a new word (UI|Button pattern) - segments += 1; - if segments > 1 { - return true; - } - prev_was_upper = true; - continue; - } - prev_was_upper = true; - } - _ => { - // lowercase or digit - continue in same segment - chars.next(); - prev_was_upper = false; - } - } - } - } - } - if segments > 1 { - return true; - } - } - false -} +fn is_multi_word(name: &str) -> bool { + // After normalisation, multi-word iff there is at least one hyphen. + // Note: we don't trim leading/trailing hyphens so `_Foo`/`Foo_` still count as multi-word, + // matching the tests in this file. + normalise_name(name).contains('-') +}
🧹 Nitpick comments (3)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (3)
106-114: Rule metadata looks solid; consider severity “warning” for a style rule.This is a style/recommendation; error might be too harsh for many codebases.
Apply this diff if you agree:
- severity: Severity::Error, + severity: Severity::Warning,
162-173: Tweak diagnostic phrasing.Lowercase “component” and tighten the sentence.
Apply this diff:
- "This Component's name "<Emphasis>"\""{component_name}"\""</Emphasis>" only contains one word." + "The component name "<Emphasis>"\""{component_name}"\""</Emphasis>" contains only one word."
292-350: Add tests to prove ignore normalisation and more edge cases.Cover kebab↔Pascal ignore matching and a few boundaries.
Here’s a small extension:
@@ fn test_should_report_user_ignores() { let mut options = UseVueMultiWordComponentNamesOptions::default(); options.ignores.push("FooBar".to_string()); // PascalCase options.ignores.push("widget".to_string()); // lowercase // PascalCase ignore and its kebab-case variant assert!(!should_report("FooBar", &options)); - assert!(!should_report("foo-bar", &options)); + assert!(!should_report("foo-bar", &options)); // kebab should match Pascal ignore // Lowercase ignore assert!(!should_report("widget", &options)); + + // Kebab ignore matches Pascal input + options.ignores.push("keep-alive".to_string()); + assert!(!should_report("KeepAlive", &options)); @@ fn test_is_multi_word() { + // kebab↔Pascal boundaries assert!(is_multi_word("MyComponent")); assert!(is_multi_word("my-component")); assert!(is_multi_word("myComponent")); assert!(is_multi_word("MyAppRoot")); assert!(is_multi_word("MYComponent")); + assert!(is_multi_word("AAa")); // acronym + word }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid-has-name.vue.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/nursery/useVueMultiWordComponentNames/invalid.js.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (2)
crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs(1 hunks)crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{rs,toml}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Format Rust and TOML files before committing (use
just f/just format).
Files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.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/src/lint/nursery/use_vue_multi_word_component_names.rs
crates/biome_*/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place core crates under /crates/biome_*/
Files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
🧠 Learnings (10)
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_js_analyze/lib/src/lint/nursery/*.rs : For new JavaScript lint rules generated by `just new-js-lintrule`, implement the rule in the generated file under `biome_js_analyze/lib/src/lint/nursery/`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : In `declare_lint_rule!` declarations, set `version: "next"`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : When porting from other linters, declare `sources: &[RuleSource::<...>]` in `declare_lint_rule!` using `.same()` or `.inspired()` as appropriate
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use `domains` in `declare_lint_rule!` when applicable; recommended rules with domains enable only when the domain is active
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : If a rule provides a code action, add `fix_kind` in `declare_lint_rule!` and use `ctx.action_category(ctx.category(), ctx.group())` and `ctx.metadata().applicability()` when constructing actions
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Place all new rules in the nursery group (implement new rule files under a `nursery` directory)
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Choose an appropriate `severity` in `declare_lint_rule!` (default is info); use `Severity::Error` for hard errors, `Warning` for potential issues, `Information` for style
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : If a rule is deprecated, set the `deprecated:` field in `declare_lint_rule!` with a reason and alternative
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/**/lib/src/**/nursery/**/*.rs : Use the local `rule_category!()` macro in diagnostics for the rule’s category, not string-parsed categories
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
📚 Learning: 2025-08-17T08:56:30.831Z
Learnt from: CR
PR: biomejs/biome#0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-08-17T08:56:30.831Z
Learning: Applies to crates/biome_analyze/crates/biome_rule_options/lib/**/*.rs : Define per-rule options in the `biome_rule_options` crate under `lib/`, with serde- and schemars-compatible derives and `#[serde(rename_all = "camelCase", deny_unknown_fields, default)]`
Applied to files:
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs
🧬 Code graph analysis (1)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (4)
crates/biome_html_syntax/src/file_source.rs (1)
vue(85-89)crates/biome_js_analyze/src/frameworks/vue/vue_component.rs (4)
name(93-105)from_potential_component(79-88)from_potential_component(135-188)new(71-73)crates/biome_js_analyze/src/frameworks/vue/vue_component/component_name.rs (1)
component_name(9-51)crates/biome_rowan/src/token_text.rs (1)
new(40-43)
⏰ 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). (25)
- GitHub Check: Test Node.js API
- GitHub Check: Test (depot-windows-2022-16)
- GitHub Check: End-to-end tests
- GitHub Check: Test (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
- GitHub Check: Lint project (depot-windows-2022)
- GitHub Check: Check Dependencies
- GitHub Check: Bench (biome_package)
- GitHub Check: Bench (biome_configuration)
- GitHub Check: Bench (biome_json_analyze)
- GitHub Check: Bench (biome_graphql_parser)
- GitHub Check: Bench (biome_module_graph)
- GitHub Check: Bench (biome_html_parser)
- GitHub Check: Bench (biome_js_analyze)
- GitHub Check: Bench (biome_graphql_formatter)
- GitHub Check: Bench (biome_html_formatter)
- GitHub Check: Bench (biome_css_analyze)
- GitHub Check: Bench (biome_json_formatter)
- GitHub Check: Bench (biome_js_formatter)
- GitHub Check: Bench (biome_js_parser)
- GitHub Check: Bench (biome_css_formatter)
- GitHub Check: Bench (biome_json_parser)
- GitHub Check: Bench (biome_css_parser)
- GitHub Check: Check JS Files
- GitHub Check: autofix
🔇 Additional comments (2)
crates/biome_js_analyze/src/lint/nursery/use_vue_multi_word_component_names.rs (2)
123-142: LGTM: query and name resolution flow is correct.Path-aware VueComponent construction, name extraction, and state shaping are sound.
188-196: Verify built-in ignores match Vue’s built-ins.Double-check parity with eslint-plugin-vue (e.g. whether
templateshould be here, and whether anything is missing). It won’t change behaviour for multi-word names, but it avoids surprises.I can cross-check upstream lists and open a follow-up if you want.
0084b2f to
71c20e6
Compare
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Summary
This PR implements a new rule
useVueMultiWordComponentNamesbased on https://eslint.vuejs.org/rules/multi-word-component-names.htmlI opted to include the
ignoreoption so that users have an escape hatch, and the logic is trivial to implement.closes #6298
Test Plan
Added tests, based on https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/multi-word-component-names.js
I couldn't just reuse all the test cases because our vue component query doesn't cover some cases that these do.
Docs