-
-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(html/nursery): add noDupeElseIfBlocks, noDupeStyleProperties, noDupeUseDirectives #10549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fbddf81
db70b98
9a1e7cf
918f981
2194d9d
6edb74c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --- | ||
| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the nursery rule [`noDuplicateElseIf`](https://biomejs.dev/linter/rules/no-duplicate-else-if/) for Svelte templates: disallow duplicate conditions in `{#if}` / `{:else if}` chains. A condition identical to a previous one can never execute: | ||
|
|
||
| ```svelte | ||
| {#if a} | ||
| <div>a</div> | ||
| {:else if a} | ||
| <div>unreachable</div> | ||
| {/if} | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the nursery rule [`noSvelteDuplicateStyleProperties`](https://biomejs.dev/linter/rules/no-svelte-duplicate-style-properties/) for Svelte templates: disallow duplicate `style:` directives on the same element. `<div style:color="red" style:color="blue"></div>` is invalid. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@biomejs/biome": patch | ||
| --- | ||
|
|
||
| Added the nursery rule [`noSvelteDuplicateUseDirectives`](https://biomejs.dev/linter/rules/no-svelte-duplicate-use-directives/) for Svelte templates: disallow duplicate `use:` directives on the same element. `<div use:tooltip use:tooltip></div>` is invalid. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| use biome_analyze::{ | ||
| Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule, | ||
| }; | ||
| use biome_console::markup; | ||
| use biome_html_syntax::SvelteIfBlock; | ||
| use biome_rowan::{AstNode, AstNodeList, TextRange}; | ||
| use biome_rule_options::no_duplicate_else_if::NoDuplicateElseIfOptions; | ||
|
|
||
| declare_lint_rule! { | ||
| /// Disallow duplicate conditions in Svelte `{#if}` / `{:else if}` chains. | ||
| /// | ||
| /// If an `{:else if}` condition is textually identical to a previous condition in the same | ||
| /// chain, it can never execute, making it dead code. | ||
| /// | ||
| /// This rule only applies to Svelte templates. It does not detect duplicate conditions in Vue or Astro templates. | ||
| /// | ||
| /// ## Examples | ||
| /// | ||
| /// ### Invalid | ||
| /// | ||
| /// ```svelte,expect_diagnostic | ||
| /// {#if a} | ||
| /// <div>a</div> | ||
| /// {:else if b} | ||
| /// <div>b</div> | ||
| /// {:else if a} | ||
| /// <div>a again</div> | ||
| /// {/if} | ||
| /// ``` | ||
| /// | ||
| /// ### Valid | ||
| /// | ||
| /// ```svelte | ||
| /// {#if a} | ||
| /// <div>a</div> | ||
| /// {:else if b} | ||
| /// <div>b</div> | ||
| /// {:else if c} | ||
| /// <div>c</div> | ||
| /// {/if} | ||
| /// ``` | ||
| /// | ||
| pub NoDuplicateElseIf { | ||
| version: "next", | ||
| name: "noDuplicateElseIf", | ||
| language: "html", | ||
| domains: &[RuleDomain::Svelte], | ||
| recommended: true, | ||
| sources: &[RuleSource::EslintSvelte("no-dupe-else-if-blocks").same()], | ||
| } | ||
| } | ||
|
|
||
| pub struct State { | ||
| /// Range of the duplicate condition. | ||
| duplicate_range: TextRange, | ||
| /// Range of the first occurrence. | ||
| original_range: TextRange, | ||
| /// The condition text. | ||
| condition: String, | ||
| } | ||
|
|
||
| impl Rule for NoDuplicateElseIf { | ||
| type Query = Ast<SvelteIfBlock>; | ||
| type State = State; | ||
| type Signals = Box<[Self::State]>; | ||
| type Options = NoDuplicateElseIfOptions; | ||
|
|
||
| fn run(ctx: &RuleContext<Self>) -> Self::Signals { | ||
| let node = ctx.query(); | ||
|
|
||
| // Collect (text, range) for each condition in the chain. | ||
| let mut conditions: Vec<(String, TextRange)> = Vec::new(); | ||
| let mut violations: Vec<State> = Vec::new(); | ||
|
|
||
| // Opening block condition. | ||
| if let Ok(opening) = node.opening_block() | ||
| && let Ok(expr) = opening.expression() | ||
| && let Ok(token) = expr.html_literal_token() | ||
| { | ||
| let text = token.text_trimmed().to_string(); | ||
| let range = token.text_trimmed_range(); | ||
| conditions.push((text, range)); | ||
| } | ||
|
|
||
| // Else-if clause conditions. | ||
| for clause in node.else_if_clauses().iter() { | ||
| let Ok(expr) = clause.expression() else { | ||
| continue; | ||
| }; | ||
| let Ok(token) = expr.html_literal_token() else { | ||
| continue; | ||
| }; | ||
| let text = token.text_trimmed().to_string(); | ||
| let range = token.text_trimmed_range(); | ||
|
|
||
| if let Some((_, original_range)) = | ||
| conditions.iter().find(|(prev_text, _)| prev_text == &text) | ||
| { | ||
| violations.push(State { | ||
| duplicate_range: clause.range(), | ||
| original_range: *original_range, | ||
| condition: text.clone(), | ||
| }); | ||
| } | ||
|
|
||
| conditions.push((text, range)); | ||
| } | ||
|
|
||
| violations.into_boxed_slice() | ||
| } | ||
|
|
||
| fn diagnostic(_ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> { | ||
| let condition = state.condition.as_str(); | ||
| Some( | ||
| RuleDiagnostic::new( | ||
| rule_category!(), | ||
| state.duplicate_range, | ||
| markup! { | ||
| "This branch can never execute. Its condition "<Emphasis>{condition}</Emphasis>" is a duplicate of a previous condition." | ||
| }, | ||
| ) | ||
| .detail( | ||
| state.original_range, | ||
| "This is the first occurrence of the condition.", | ||
| ) | ||
| .note(markup! { | ||
| "Remove the duplicate "<Emphasis>"{:else if}"</Emphasis>" branch or change its condition to make it reachable." | ||
| }), | ||
| ) | ||
| } | ||
|
Comment on lines
+112
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The diagnostic must explain:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, link to it https://biomejs.dev/linter/#rule-pillars |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
End the sentence with a full stop.
Tiny docs nit: Line 5 currently ends with a colon; the changeset style requires a full stop at sentence end.
As per coding guidelines: “End every sentence in a changeset with a full stop (
.).”🤖 Prompt for AI Agents