diff --git a/.changeset/add-no-undeclared-styles.md b/.changeset/add-no-undeclared-styles.md new file mode 100644 index 000000000000..47da58de1380 --- /dev/null +++ b/.changeset/add-no-undeclared-styles.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": minor +--- + +Added new nursery lint rule [`noUndeclaredStyles`](https://biomejs.dev/linter/rules/no-undeclared-styles/) for HTML. The rule detects CSS class names used in `class="..."` attributes that are not defined in any ` + + +
+ + +``` diff --git a/.changeset/add-no-unused-styles.md b/.changeset/add-no-unused-styles.md new file mode 100644 index 000000000000..3aa26b9e0eb1 --- /dev/null +++ b/.changeset/add-no-unused-styles.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": minor +--- + +Added new nursery lint rule [`noUnusedStyles`](https://biomejs.dev/linter/rules/no-unused-styles/) for CSS. The rule detects CSS class selectors that are never referenced in any HTML or JSX file that imports the stylesheet. This is a project-domain rule that requires the module graph. + +```css +/* styles.css — .ghost is never used in any importing file */ +.button { color: blue; } +.ghost { color: red; } +``` + +```jsx +/* App.jsx */ +import "./styles.css"; +export default () =>
; +``` diff --git a/.claude/skills/README.md b/.claude/skills/README.md index d06f9eab90ef..de5e9e8e4554 100644 --- a/.claude/skills/README.md +++ b/.claude/skills/README.md @@ -13,6 +13,22 @@ This directory contains specialized skills for AI coding assistants working on B Skills complement the specialized **agents** in `.claude/agents/` - agents are personas that do the work, skills are the procedural knowledge they reference. +## Universal Coding Standards + +**CRITICAL: No Emojis Policy** + +Emojis are BANNED in all code contributions and documentation: +- NO emojis in source code +- NO emojis in comments (code comments, rustdoc, etc.) +- NO emojis in diagnostic messages +- NO emojis in test files +- NO emojis in commit messages +- NO emojis in PR descriptions +- NO emojis in skill documents or agent instructions +- NO emojis in any generated code or text + +This applies to all agents, all skills, and all contributions. Keep code and documentation professional and emoji-free. + ## Available Skills ### Core Development Skills diff --git a/.claude/skills/biome-developer/SKILL.md b/.claude/skills/biome-developer/SKILL.md index 9f8f3adabf2e..6ce30511d3d8 100644 --- a/.claude/skills/biome-developer/SKILL.md +++ b/.claude/skills/biome-developer/SKILL.md @@ -13,19 +13,65 @@ This skill provides general development best practices, common gotchas, and Biom - Understanding of Biome's architecture (parser, analyzer, formatter) - Development environment set up (see CONTRIBUTING.md) +## Universal Code Standards + +### CRITICAL: No Emojis Policy + +**Emojis are absolutely BANNED in all code contributions.** + +This applies to: +- Source code (Rust, JavaScript, TypeScript, etc.) +- Code comments and documentation (inline comments, rustdoc, JSDoc, etc.) +- Diagnostic messages and error text +- Test files and test data +- Commit messages +- Pull request titles and descriptions +- Any generated code or scaffolding +- Configuration files and JSON data + +**Why:** +- Professional codebase standards +- Consistency across the project +- Avoid encoding/rendering issues +- Keep communication clear and technical + +**Examples:** + +```rust +// Bad: WRONG - Contains emoji +/// This function is super cool! +fn calculate() { } + +// Good: CORRECT - No emoji +/// Calculates the optimal value using binary search. +fn calculate() { } +``` + +```rust +// Bad: WRONG - Emoji in diagnostic +markup! { "This is not allowed!" } + +// Good: CORRECT - Clear text +markup! { "This syntax is not allowed." } +``` + +**Enforcement:** All agents and contributors must follow this rule. No exceptions. + ## Common Gotchas and Best Practices ### Working with AST and Syntax Nodes **DO:** -- ✅ Use parser crate's `quick_test` to inspect AST structure before implementing -- ✅ Understand the node hierarchy and parent-child relationships -- ✅ Check both general cases AND specific types (e.g., Vue has both `VueDirective` and `VueV*ShorthandDirective`) -- ✅ Verify your solution works for all relevant variant types, not just the first one you find +- Use parser crate's `quick_test` to inspect AST structure before implementing +- Understand the node hierarchy and parent-child relationships +- Check both general cases AND specific types (e.g., Vue has both `VueDirective` and `VueV*ShorthandDirective`) +- Verify your solution works for all relevant variant types, not just the first one you find +- Extract helper functions that return `Option` or `SyntaxResult` instead of scattering early returns throughout the caller — this makes code more readable and composable **DON'T:** -- ❌ Build the full Biome binary just to inspect syntax (expensive) - use parser crate's `quick_test` instead -- ❌ Assume syntax patterns without inspecting the AST first +- Build the full Biome binary just to inspect syntax (expensive) - use parser crate's `quick_test` instead +- Assume syntax patterns without inspecting the AST first +- Write functions with many `let Ok(...) else { return }` scattered throughout when you can extract a helper that returns `Option` instead **Example - Inspecting AST:** ```rust @@ -43,16 +89,64 @@ pub fn quick_test() { Run: `just qt biome_html_parser` +**Example - Extracting CST Navigation Logic:** +```rust +// WRONG: Many early returns scattered in the caller +fn visit_attribute(&self, attr: JsxAttribute, collector: &mut Collector) { + let Ok(name_node) = attr.name() else { return }; + let name_text = match name_node { + AnyJsxAttributeName::JsxName(n) => match n.value_token() { + Ok(t) => t.token_text_trimmed(), + Err(_) => return, + }, + AnyJsxAttributeName::JsxNamespaceName(_) => return, + }; + if name_text != "class" && name_text != "className" { + return; + } + let Some(jsx_string) = attr.initializer().and_then(|i| i.value().ok()) else { + return; + }; + // ... do the real work +} + +// CORRECT: Extract helper that returns Option +fn visit_attribute(&self, attr: JsxAttribute, collector: &mut Collector) { + if let Some(inner) = self.extract_class_attribute_inner(&attr) { + self.collect_classes(&inner, collector); + } +} + +fn extract_class_attribute_inner(&self, attr: &JsxAttribute) -> Option { + let name_node = attr.name().ok()?; + let name_text = match name_node { + AnyJsxAttributeName::JsxName(n) => n.value_token().ok()?.token_text_trimmed(), + AnyJsxAttributeName::JsxNamespaceName(_) => return None, + }; + if name_text != "class" && name_text != "className" { + return None; + } + let jsx_string = attr.initializer().and_then(|i| i.value().ok())?; + jsx_string.inner_string_text().ok() +} +``` + +The helper uses `?` operator and `Option` combinators — much cleaner than scattered `else { return }` blocks. The caller now has a single `if let Some` that clearly expresses intent. + ### String Extraction and Text Handling **DO:** -- ✅ Use `inner_string_text()` when extracting content from quoted strings (removes quotes) -- ✅ Use `text_trimmed()` when you need the full token text without leading/trailing whitespace -- ✅ Use `token_text_trimmed()` on nodes like `HtmlAttributeName` to get the text content -- ✅ Verify whether values use `HtmlString` (quotes) or `HtmlTextExpression` (curly braces) +- Use `inner_string_text()` when extracting content from quoted strings — it strips the surrounding quotes and returns a `TokenText` backed by the same green token (no allocation) +- Use `text_trimmed()` when you need the full token text without leading/trailing whitespace +- Use `token_text_trimmed()` on nodes like `HtmlAttributeName` to get the text content +- Verify whether values use `HtmlString` (quotes) or `HtmlTextExpression` (curly braces) +- Use `TokenText::slice()` or `inner_string_text()` to get sub-ranges of a token — both return a `TokenText` backed by the same `GreenToken` (ref-count bump only, no heap allocation) **DON'T:** -- ❌ Use `text_trimmed()` when you need `inner_string_text()` for extracting quoted string contents +- Use `text_trimmed()` when you need `inner_string_text()` for extracting quoted string contents +- Call `.text()` on a `SyntaxToken` — it returns raw text including surrounding trivia (whitespace, newlines). Always use `.text_trimmed()` instead. +- Strip quotes manually with `&s[1..s.len()-1]` — use `inner_string_text()` instead; it is correct, allocation-free, and communicates intent +- Use `word.to_string()` or `String::from(word)` to store individual words split out of a string token — store the `TokenText` of the whole token plus a token-relative `TextRange` instead (see below) **Example - String Extraction:** ```rust @@ -66,17 +160,67 @@ let inner_text = html_string.inner_string_text().ok()?; let content = inner_text.text(); // Returns: "handler" ``` +**Example - CSS class name extraction from `CssClassSelector`:** +```rust +// WRONG: .text() includes trivia +let name = selector.name().ok()?.value_token().ok()?.text(); // may include whitespace + +// CORRECT: always use text_trimmed() on SyntaxToken +let name: &str = selector.name().ok()?.value_token().ok()?.text_trimmed(); +// For owned value: +let name: TokenText = selector.name().ok()?.value_token().ok()?.token_text_trimmed(); +``` + +### Storing Split Token Words Without Allocation + +When you need to split a string token (e.g. `class="foo bar baz"`) into individual words and store each word, do **not** allocate a `String` per word. Instead, store the `TokenText` of the whole token and a `TextRange` that is **relative to the token text** (not the file). + +```rust +// WRONG: allocates a String per word +for word in content.split_ascii_whitespace() { + collected.push(word.to_string()); // heap allocation per word +} + +// CORRECT: store token + token-relative range +// Use inner_string_text() to get the quote-stripped TokenText first. +let inner: TokenText = html_string.inner_string_text()?; +let content = inner.text(); +let mut offset: u32 = 0; +for word in content.split_ascii_whitespace() { + let word_offset = content[offset as usize..] + .find(word) + .map_or(offset, |pos| offset + pos as u32); + let start = TextSize::from(word_offset); + let end = start + TextSize::from(word.len() as u32); + collected.push(MyEntry { + token: inner.clone(), // refcount bump only + range: TextRange::new(start, end), + }); + offset = word_offset + word.len() as u32; +} + +// Later, to read the word back: +fn text(&self) -> &str { + &self.token.text()[usize::from(self.range.start())..usize::from(self.range.end())] +} +``` + +Key points: +- `inner_string_text()` returns a `TokenText` whose `.text()` starts at byte 0 of the unquoted content. Word offsets within that are directly usable as token-relative ranges. +- `TokenText::clone()` is a refcount bump on the underlying `GreenToken` — it does not copy string data. +- To produce file-level diagnostic ranges from token-relative ranges, add the token's absolute file offset: `u32::from(value_token.text_trimmed_range().start()) + 1` (the `+1` skips the opening quote). + ### Working with Embedded Languages **DO:** -- ✅ Verify changes work for different value formats (quoted strings vs text expressions) when handling multiple frameworks -- ✅ Use appropriate `EmbeddingKind` for context (Vue, Svelte, Astro, etc.) -- ✅ Check if embedded content needs `is_source: true` (script tags) vs `is_source: false` (template expressions) -- ✅ Calculate offsets correctly: token start + 1 for opening quote, or use `text_range().start()` for text expressions +- Verify changes work for different value formats (quoted strings vs text expressions) when handling multiple frameworks +- Use appropriate `EmbeddingKind` for context (Vue, Svelte, Astro, etc.) +- Check if embedded content needs `is_source: true` (script tags) vs `is_source: false` (template expressions) +- Calculate offsets correctly: token start + 1 for opening quote, or use `text_range().start()` for text expressions **DON'T:** -- ❌ Assume all frameworks use the same syntax (Vue uses quotes, Svelte uses curly braces) -- ❌ Implement features for "widely used" patterns without evidence - ask the user first +- Assume all frameworks use the same syntax (Vue uses quotes, Svelte uses curly braces) +- Implement features for "widely used" patterns without evidence - ask the user first **Example - Different Value Formats:** ```rust @@ -92,11 +236,11 @@ let expression = text_expression.expression().ok()?; ### Borrow Checker and Temporary Values **DO:** -- ✅ Use intermediate `let` bindings to avoid temporary value borrows that get dropped -- ✅ Store method results that return owned values before calling methods on them +- Use intermediate `let` bindings to avoid temporary value borrows that get dropped +- Store method results that return owned values before calling methods on them **DON'T:** -- ❌ Create temporary value borrows that get dropped before use +- Create temporary value borrows that get dropped before use **Example - Avoiding Borrow Issues:** ```rust @@ -113,12 +257,12 @@ let token = html_string.value_token().ok()?; // OK ### Clippy and Code Style **DO:** -- ✅ Use `let` chains to collapse nested `if let` statements (cleaner and follows Rust idioms) -- ✅ Run `just l` before committing to catch clippy warnings -- ✅ Fix clippy suggestions unless there's a good reason not to +- Use `let` chains to collapse nested `if let` statements (cleaner and follows Rust idioms) +- Run `just l` before committing to catch clippy warnings +- Fix clippy suggestions unless there's a good reason not to **DON'T:** -- ❌ Ignore clippy warnings - they often catch real issues or suggest better patterns +- Ignore clippy warnings - they often catch real issues or suggest better patterns **Example - Collapsible If:** ```rust @@ -140,13 +284,13 @@ if let Some(directive) = VueDirective::cast_ref(&element) ### Legacy and Deprecated Syntax **DO:** -- ✅ Ask users before implementing deprecated/legacy syntax support -- ✅ Wait for user demand before spending time on legacy features -- ✅ Document when features are intentionally not supported due to being legacy +- Ask users before implementing deprecated/legacy syntax support +- Wait for user demand before spending time on legacy features +- Document when features are intentionally not supported due to being legacy **DON'T:** -- ❌ Implement legacy/deprecated syntax without checking with the user first -- ❌ Claim patterns are "widely used" or "common" without evidence +- Implement legacy/deprecated syntax without checking with the user first +- Claim patterns are "widely used" or "common" without evidence **Example:** Svelte's `on:click` event handler syntax is legacy (Svelte 3/4). Modern Svelte 5 runes mode uses regular attributes. Unless users specifically request it, don't implement legacy syntax support. @@ -154,15 +298,15 @@ Svelte's `on:click` event handler syntax is legacy (Svelte 3/4). Modern Svelte 5 ### Testing and Development **DO:** -- ✅ Use `just qt ` to run quick tests (handles test execution automatically) -- ✅ Review snapshot changes carefully - don't blindly accept -- ✅ Test with multiple variants when working with enums (e.g., all `VueV*ShorthandDirective` types) -- ✅ Add tests for both valid and invalid cases -- ✅ Use CLI tests for testing embedded languages (Vue/Svelte directives, etc.) +- Use `just qt ` to run quick tests (handles test execution automatically) +- Review snapshot changes carefully - don't blindly accept +- Test with multiple variants when working with enums (e.g., all `VueV*ShorthandDirective` types) +- Add tests for both valid and invalid cases +- Use CLI tests for testing embedded languages (Vue/Svelte directives, etc.) **DON'T:** -- ❌ Blindly accept all snapshot changes -- ❌ Try to test embedded languages in analyzer packages (they don't have embedding capabilities) +- Blindly accept all snapshot changes +- Try to test embedded languages in analyzer packages (they don't have embedding capabilities) ## Pattern Matching Tips @@ -210,10 +354,20 @@ if let Some(directive) = VueDirective::cast_ref(&element) { | Method | Use When | Returns | | --- | --- | --- | -| `inner_string_text()` | Extracting content from quoted strings | Content without quotes | -| `text_trimmed()` | Getting token text without whitespace | Full token text | -| `token_text_trimmed()` | Getting text from nodes like `HtmlAttributeName` | Node text content | -| `text()` | Getting raw text | Exact text as written | +| `inner_string_text()` | Extracting content from quoted strings | Content without quotes, as `TokenText` (no alloc) | +| `text_trimmed()` | Getting token text without whitespace | `&str` — full token text | +| `token_text_trimmed()` | Getting an owned, cloneable token text | `TokenText` — backed by green token | +| `text()` | Getting raw text including trivia | `&str` — exact text as written | + +### `Text` vs `TokenText` vs `String` + +| Type | Size | Clone cost | Use when | +| --- | --- | --- | --- | +| `TokenText` | 16 bytes | Refcount bump | You have a `SyntaxToken` and want allocation-free ownership | +| `Text` | 16 bytes | Refcount bump (token) or heap copy (owned) | Union of `TokenText` and an owned string — use when the source may not be a token | +| `String` | 24 bytes | Heap copy | Only when you actually need an owned, mutable string (e.g. for a diagnostic message) | + +`Text` is the richer type: `From` is implemented, so a `TokenText` can always be cheaply wrapped in `Text`. When storing data extracted directly from a syntax token, prefer `TokenText` or the token+range pattern. ### Value Extraction Methods @@ -233,12 +387,12 @@ if let Some(directive) = VueDirective::cast_ref(&element) { ## Documentation and Markdown Formatting **DO:** -- ✅ Use spaces around table separators: `| --- | --- | --- |` (not `|---|---|---|`) -- ✅ Ensure all Markdown tables follow "compact" style with proper spacing -- ✅ Test documentation changes with markdown linters before committing +- Use spaces around table separators: `| --- | --- | --- |` (not `|---|---|---|`) +- Ensure all Markdown tables follow "compact" style with proper spacing +- Test documentation changes with markdown linters before committing **DON'T:** -- ❌ Use compact table separators without spaces (causes CI linting failures) +- Use compact table separators without spaces (causes CI linting failures) **Example - Table Formatting:** ```markdown diff --git a/.claude/skills/diagnostics-development/SKILL.md b/.claude/skills/diagnostics-development/SKILL.md index d872547c1d6f..25d98fea9f2e 100644 --- a/.claude/skills/diagnostics-development/SKILL.md +++ b/.claude/skills/diagnostics-development/SKILL.md @@ -25,6 +25,16 @@ Use this skill when creating diagnostics - the error messages, warnings, and hin - Actionable: Always suggest how to fix - Show don't tell: Prefer code frames over textual explanations +**CRITICAL: No Emojis in Diagnostics** + +Emojis are BANNED in all diagnostic messages, advice text, and error output: +- NO emojis in diagnostic messages +- NO emojis in advice notes +- NO emojis in code frame annotations +- NO emojis in log messages + +Keep all user-facing text professional and emoji-free. + ## Common Workflows ### Create a Diagnostic Type @@ -240,25 +250,25 @@ Available tags: **Good messages:** ```rust -// ✅ Specific and actionable +// Good: Specific and actionable "Use 'let' or 'const' instead of 'var'" -// ✅ Explains why +// Good: Explains why "This variable is never reassigned, consider using 'const'" -// ✅ Shows what to do +// Good: Shows what to do "Remove the unused import statement" ``` **Bad messages:** ```rust -// ❌ Too vague +// Bad: Too vague "Invalid syntax" -// ❌ Just states the obvious +// Bad: Just states the obvious "Variable declared with 'var'" -// ❌ No guidance +// Bad: No guidance "This code has a problem" ``` @@ -266,14 +276,14 @@ Available tags: **Show, don't tell:** ```rust -// ✅ Good - shows code frame +// Good: Good - shows code frame CodeFrameAdvice { location: node.range(), source_code: source, annotation: markup! { "This expression is always truthy" } } -// ❌ Less helpful - just text +// Bad: Less helpful - just text LogAdvice { message: markup! { "The expression at line 5 is always truthy" } } @@ -281,13 +291,13 @@ LogAdvice { **Provide actionable fixes:** ```rust -// ✅ Good - shows exact change +// Good: Good - shows exact change DiffAdvice { old: "var x = 1;", new: "const x = 1;", } -// ❌ Less helpful - describes change +// Bad: Less helpful - describes change LogAdvice { message: markup! { "Change 'var' to 'const'" } } diff --git a/.claude/skills/formatter-development/SKILL.md b/.claude/skills/formatter-development/SKILL.md index aabc2dc045c4..92f08f42906c 100644 --- a/.claude/skills/formatter-development/SKILL.md +++ b/.claude/skills/formatter-development/SKILL.md @@ -13,6 +13,18 @@ Use this skill when implementing or modifying Biome's formatters. It covers the 2. Language-specific crates must exist: `biome_{lang}_syntax`, `biome_{lang}_formatter` 3. For Prettier comparison: Install `bun` and run `pnpm install` in repo root +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all formatter code: +- NO emojis in code comments +- NO emojis in rustdoc documentation +- NO emojis in test files +- NO emojis in debug output or error messages + +Keep all code professional and emoji-free. + ## Common Workflows ### Generate Formatter Boilerplate diff --git a/.claude/skills/lint-rule-development/SKILL.md b/.claude/skills/lint-rule-development/SKILL.md index 79a513071aea..32cba6a307ca 100644 --- a/.claude/skills/lint-rule-development/SKILL.md +++ b/.claude/skills/lint-rule-development/SKILL.md @@ -13,6 +13,19 @@ Use this skill when creating new lint rules or assist actions for Biome. It prov 2. Ensure `cargo`, `just`, and `pnpm` are available 3. Read `crates/biome_analyze/CONTRIBUTING.md` for in-depth concepts +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all lint rule code: +- NO emojis in rustdoc comments +- NO emojis in diagnostic messages +- NO emojis in code action descriptions +- NO emojis in test files or test comments +- NO emojis anywhere in the rule implementation + +Keep all code and documentation professional and emoji-free. + ## Common Workflows ### Create a New Lint Rule @@ -74,18 +87,58 @@ impl Rule for UseMyRuleName { RuleDiagnostic::new( rule_category!(), node.range(), + // Pillar 1 — WHAT the error is. markup! { - "Avoid using this identifier." + "This identifier ""prohibited_name"" is not allowed." }, ) + // Pillar 2 — WHY it is triggered / why it is a problem. .note(markup! { - "This identifier is prohibited because..." + "Using this identifier leads to [specific problem]." + }) + // Pillar 3 — WHAT the user should do to fix it. + // Use a code action instead when an automated fix is possible. + .note(markup! { + "Replace it with [alternative] or remove it entirely." }), ) } } ``` +### The Three Diagnostic Pillars (REQUIRED) + +Every diagnostic **must** follow the three pillars defined in `crates/biome_analyze/CONTRIBUTING.md`: + +| Pillar | Question answered | Implemented as | +| --- | --- | --- | +| 1 | **What** is the error? | The `RuleDiagnostic` message (first argument to `markup!`) | +| 2 | **Why** is it a problem? | A `.note()` explaining the consequence or rationale | +| 3 | **What should the user do?** | A code action (`action` fn), or a second `.note()` if no fix is available | + +**Example from `noUnusedVariables`:** +```rust +RuleDiagnostic::new( + rule_category!(), + range, + // Pillar 1: what + markup! { "This variable "{name}" is unused." }, +) +// Pillar 2: why +.note(markup! { + "Unused variables are often the result of typos, incomplete refactors, or other sources of bugs." +}) +// Pillar 3: what to do (here as a note; ideally a code action) +.note(markup! { + "Remove the variable or use it." +}) +``` + +**Common mistakes to avoid:** +- Combining pillars 2 and 3 into a single note — keep them separate. +- Writing pillar 3 as the only note, skipping pillar 2. +- Writing a pillar 1 message that already contains "why" — the message should stay short and factual; move the rationale to pillar 2. + ### Using Semantic Model For rules that need binding analysis: @@ -187,12 +240,51 @@ tests/specs/nursery/useMyRuleName/ └── options.json # Optional rule configuration ``` +**IMPORTANT: Magic Comments for Test Expectations** + +All test files MUST include magic comments at the top to set expectations: + +- **Valid tests** (should not generate diagnostics): +```javascript +/* should not generate diagnostics */ +const allowed_name = 1; +``` + +- **Invalid tests** (should generate diagnostics): +```javascript +// should generate diagnostics +const prohibited_name = 1; +const another_prohibited = 2; +``` + +For HTML files: +```html + + +... +``` + +For languages that support both comment styles, use `/* */` or `//` as appropriate. The comment should be the very first line of the file. + +These magic comments: +- Document the intent of the test file +- Help reviewers understand what's expected +- Serve as a quick reference when debugging test failures + Example `invalid.js`: ```javascript +// should generate diagnostics const prohibited_name = 1; const another_prohibited = 2; ``` +Example `valid.js`: +```javascript +/* should not generate diagnostics */ +const allowed_name = 1; +const another_allowed = 2; +``` + Run snapshot tests: ```shell just test-lintrule useMyRuleName @@ -225,6 +317,71 @@ just f # Format code just l # Lint code ``` +## Documenting Rules + +### Multi-File Examples + +For rules that analyze relationships between multiple files (e.g., import cycles, cross-file dependencies, CSS class references), use the `file=` property: + +```rust +/// ### Invalid +/// +/// ```js,expect_diagnostic,file=foo.js +/// import { bar } from "./bar.js"; +/// export function foo() { +/// return bar(); +/// } +/// ``` +/// +/// ```js,expect_diagnostic,file=bar.js +/// import { foo } from "./foo.js"; +/// export function bar() { +/// return foo(); +/// } +/// ``` +``` + +**How it works:** +- All files in a documentation section (`### Invalid` or `### Valid`) are collected into an in-memory file system +- The module graph is automatically populated from these files +- Each file with `expect_diagnostic` must emit exactly one diagnostic +- Files without `expect_diagnostic` provide context but aren't expected to trigger the rule + +**Supported languages:** +- Supported: JavaScript/TypeScript/JSX/TSX +- Supported: CSS (module graph automatically populated from `file=` blocks) +- Not supported: HTML with ` +///
Content
+/// ``` +``` + +**When to use `ignore`:** +- Rules that require features not yet available in rustdoc context (e.g., HTML embedded snippets) +- Examples that need special parser configuration +- Illustrative examples where automatic validation isn't critical + +**Important:** Even with `ignore`, you should have comprehensive snapshot tests in `tests/specs/`. + +### Code Block Property Order + +For consistency, properties should be ordered: +```rust +/// ```[,expect_diagnostic][,(options|full_options|use_options)][,ignore][,file=path] +``` + +Examples: +- `css,expect_diagnostic,file=styles.css` +- `jsx,file=App.jsx` +- `html,expect_diagnostic,ignore` + ## Tips - **Rule naming**: Use `no*` prefix for rules that forbid something (e.g., `noVar`), `use*` for rules that mandate something (e.g., `useConst`) diff --git a/.claude/skills/parser-development/SKILL.md b/.claude/skills/parser-development/SKILL.md index 169f9dea5faa..22e549ac9b2d 100644 --- a/.claude/skills/parser-development/SKILL.md +++ b/.claude/skills/parser-development/SKILL.md @@ -13,6 +13,19 @@ Use this skill when creating or modifying Biome's parsers. Covers grammar author 2. Understand the language syntax you're implementing 3. Read `crates/biome_parser/CONTRIBUTING.md` for detailed concepts +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all parser code: +- NO emojis in code comments +- NO emojis in rustdoc documentation +- NO emojis in grammar files (.ungram) +- NO emojis in test files +- NO emojis in error messages or diagnostics + +Keep all code professional and emoji-free. + ## Common Workflows ### Create Grammar for New Language diff --git a/.claude/skills/prettier-compare/SKILL.md b/.claude/skills/prettier-compare/SKILL.md index 9fc0ba044b12..9a4ef97ea6f1 100644 --- a/.claude/skills/prettier-compare/SKILL.md +++ b/.claude/skills/prettier-compare/SKILL.md @@ -13,6 +13,17 @@ Use `packages/prettier-compare/` to inspect any differences between Biome and Pr 2. Use `bun` (the CLI is a Bun script) and ensure dependencies have been installed. 3. Always pass `--rebuild` so the Biome WASM bundle matches your current Rust changes. +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all Prettier comparison code and output: +- NO emojis in comparison scripts +- NO emojis in test files +- NO emojis in output or diagnostic messages + +Keep all code professional and emoji-free. + ## Common workflows Snippets passed as CLI args: diff --git a/.claude/skills/rule-options/SKILL.md b/.claude/skills/rule-options/SKILL.md index 54423c9397a8..755e9eb8972d 100644 --- a/.claude/skills/rule-options/SKILL.md +++ b/.claude/skills/rule-options/SKILL.md @@ -13,6 +13,18 @@ Use this skill when implementing configurable options for lint rules. Covers def 2. Options must follow [Technical Philosophy](https://biomejs.dev/internals/philosophy/#technical) 3. Rule must be implemented before adding options +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all rule options code: +- NO emojis in code comments +- NO emojis in rustdoc documentation +- NO emojis in JSON schema descriptions +- NO emojis in test files (options.json, etc.) + +Keep all code professional and emoji-free. + ## Common Workflows ### Define Rule Options Type @@ -269,12 +281,12 @@ This updates: ### Option Naming ```rust -// ✅ Good - clear, semantic names +// Good: Good - clear, semantic names allow_single_line: bool max_depth: u8 ignore_patterns: Box<[Box]> -// ❌ Bad - unclear, technical names +// Bad: Bad - unclear, technical names flag: bool n: u8 list: Vec diff --git a/.claude/skills/testing-codegen/SKILL.md b/.claude/skills/testing-codegen/SKILL.md index 79f6d1411d92..6169f8ff2941 100644 --- a/.claude/skills/testing-codegen/SKILL.md +++ b/.claude/skills/testing-codegen/SKILL.md @@ -13,6 +13,18 @@ Use this skill for testing, code generation, and preparing contributions. Covers 2. Install pnpm: `corepack enable` and `pnpm install` in repo root 3. Understand which changes require code generation +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all test and generated code: +- NO emojis in test files +- NO emojis in snapshot files +- NO emojis in changeset files +- NO emojis in generated code or scaffolding + +Keep all code and documentation professional and emoji-free. + ## Common Workflows ### Run Tests diff --git a/.claude/skills/type-inference/SKILL.md b/.claude/skills/type-inference/SKILL.md index 5d1b7ee868a8..163312332080 100644 --- a/.claude/skills/type-inference/SKILL.md +++ b/.claude/skills/type-inference/SKILL.md @@ -13,6 +13,18 @@ Use this skill when working with Biome's type inference system and module graph. 2. Understand Biome's focus on IDE support and instant updates 3. Familiarity with TypeScript type system concepts +## Code Standards + +**CRITICAL: No Emojis** + +Emojis are BANNED in all type inference code: +- NO emojis in code comments +- NO emojis in rustdoc documentation +- NO emojis in test files +- NO emojis in debug output or error messages + +Keep all code professional and emoji-free. + ## Key Concepts ### Module Graph Constraint @@ -291,6 +303,100 @@ match resolved.as_raw_data() { } ``` +## CSS and HTML Module Graph + +The module graph tracks not only JS imports/exports but also CSS class names and HTML class references, used by cross-file lint rules like `noUnusedStyles` and `noUndeclaredStyles`. + +### Key Types + +``` +CssModuleInfo — classes: IndexSet +HtmlModuleInfo — style_classes: IndexSet (from + +
Single unknown
+ + + +
+ +
+ +"#, + ); + fs.create_file( + "index.html", + r#" + + +

Linked stylesheet

+ +"#, + ); + + let result = run_cli_with_dyn_fs( + Box::new(fs.create_os()), + &mut console, + Args::from(["lint", "--only=nursery/noUndeclaredStyles", fs.cli_path()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_undeclared_styles_reports_undeclared_classes", + fs.create_mem(), + console, + result, + )); +} + +/// Declared classes produce no diagnostics across all supported declaration +/// sources: inline ` +
Inline style
+ +"#, + ); + fs.create_file( + "index.html", + r#" + + +

Linked stylesheet

+ +"#, + ); + + let result = run_cli_with_dyn_fs( + Box::new(fs.create_os()), + &mut console, + Args::from(["lint", "--only=nursery/noUndeclaredStyles", fs.cli_path()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_undeclared_styles_passes_when_declared", + fs.create_mem(), + console, + result, + )); +} + +/// A file with no ` +
Inline style
+ + +``` + +## `index.html` + +```html + + + +

Linked stylesheet

+ + +``` + +## `styles.css` + +```css +.hero { + font-size: 2rem; +} +``` + +# Emitted Messages + +```block +Checked 4 files in