diff --git a/.changeset/add-no-undeclared-styles.md b/.changeset/add-no-undeclared-styles.md new file mode 100644 index 000000000000..99e79d6389b2 --- /dev/null +++ b/.changeset/add-no-undeclared-styles.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": minor +--- + +Added new nursery lint rule [`noUndeclaredClasses`](https://biomejs.dev/linter/rules/no-undeclared-classes/) for HTML, JSX, and SFC files (Vue, Astro, Svelte). The rule detects CSS class names used in `class="..."` (or `className`) 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..ab8838ce7c20 --- /dev/null +++ b/.changeset/add-no-unused-styles.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": minor +--- + +Added new nursery lint rule [`noUnusedClasses`](https://biomejs.dev/linter/rules/no-unused-classes/) 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/.changeset/add-span-conversion-helper.md b/.changeset/add-span-conversion-helper.md new file mode 100644 index 000000000000..78873ccb70de --- /dev/null +++ b/.changeset/add-span-conversion-helper.md @@ -0,0 +1,17 @@ +--- +"@biomejs/js-api": minor +--- + +Added a new `spanInBytesToSpanInCodeUnits` helper function to convert byte-based spans from Biome diagnostics to UTF-16 code unit spans. + +Biome internally uses UTF-8 byte offsets for spans, but JavaScript strings use UTF-16 code units. This causes incorrect text extraction when using `string.slice()` with non-ASCII content. The new helper function correctly handles this conversion, including surrogate pairs and unpaired surrogates. + +```js +import { spanInBytesToSpanInCodeUnits } from "@biomejs/js-api"; + +const [start, end] = spanInBytesToSpanInCodeUnits( + diagnostic.location.span, + content +); +const text = content.slice(start, end); // Correctly extracts the text +``` diff --git a/.changeset/add-use-baseline-css-rule.md b/.changeset/add-use-baseline-css-rule.md new file mode 100644 index 000000000000..0c1f42d1b01e --- /dev/null +++ b/.changeset/add-use-baseline-css-rule.md @@ -0,0 +1,11 @@ +--- +"@biomejs/biome": patch +--- + +Added new nursery lint rule `useBaseline` for CSS. The rule reports when CSS properties, property values, at-rules, media conditions, functions, or pseudo-selectors are not part of the configured [Baseline](https://developer.mozilla.org/en-US/docs/Glossary/Baseline/Compatibility) tier. + +For example, *at the time of writing*, the rule will trigger for the use of `accent-color` because it has limited availability: + +```css +a { accent-color: bar; } +``` diff --git a/.changeset/add-use-imports-first.md b/.changeset/add-use-imports-first.md new file mode 100644 index 000000000000..49640af4cd13 --- /dev/null +++ b/.changeset/add-use-imports-first.md @@ -0,0 +1,17 @@ +--- +"@biomejs/biome": patch +--- + +Added the nursery rule [`useImportsFirst`](https://biomejs.dev/linter/rules/use-imports-first/) that enforces all import statements appear before any non-import statements in a module. Inspired by the eslint-plugin-import [`import/first`](https://github.com/import-js/eslint-plugin-import/blob/HEAD/docs/rules/first.md) rule. + +```js +// Invalid +import { foo } from "foo"; +const bar = 1; +import { baz } from "baz"; // ← flagged + +// Valid +import { foo } from "foo"; +import { baz } from "baz"; +const bar = 1; +``` diff --git a/.changeset/common-fans-prove.md b/.changeset/common-fans-prove.md new file mode 100644 index 000000000000..7954b87cd62f --- /dev/null +++ b/.changeset/common-fans-prove.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": minor +--- + +The `init` command now prints the Biome logo. diff --git a/.changeset/cool-rice-change.md b/.changeset/cool-rice-change.md new file mode 100644 index 000000000000..6a592020586d --- /dev/null +++ b/.changeset/cool-rice-change.md @@ -0,0 +1,7 @@ +--- +"@biomejs/biome": minor +--- + +Added the assist action [`organizePackageJson`](https://biomejs.dev/assist/actions/organize-package-json/). + +This action organizes package.json fields according to the same conventions as the popular [sort-package-json](https://github.com/keithamus/sort-package-json) tool. diff --git a/.changeset/curly-games-follow.md b/.changeset/curly-games-follow.md new file mode 100644 index 000000000000..f3870f7bbe24 --- /dev/null +++ b/.changeset/curly-games-follow.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9432](https://github.com/biomejs/biome/issues/9432): Values referenced as a JSX element in Astro/Vue/Svelte templates are now correctly detected; `noUnusedImports` and `useImportType` rules no longer reports these values as false positives. diff --git a/.changeset/fix-css-unicode-escape-in-string.md b/.changeset/fix-css-unicode-escape-in-string.md new file mode 100644 index 000000000000..4d67d13b49e3 --- /dev/null +++ b/.changeset/fix-css-unicode-escape-in-string.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9385](https://github.com/biomejs/biome/issues/9385): [`noUselessEscapeInString`](https://biomejs.dev/linter/rules/no-useless-escape-in-string/) no longer incorrectly flags valid CSS hex escapes (e.g. `\e7bb`) as useless. The rule now recognizes all hex digits (`0-9`, `a-f`, `A-F`) as valid escape characters in CSS strings. diff --git a/.changeset/fix-embedded-template-crash.md b/.changeset/fix-embedded-template-crash.md new file mode 100644 index 000000000000..e261607eacb1 --- /dev/null +++ b/.changeset/fix-embedded-template-crash.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9131](https://github.com/biomejs/biome/issues/9131), [#9112](https://github.com/biomejs/biome/issues/9112), [#9166](https://github.com/biomejs/biome/issues/9166): the formatter no longer crashes or produces corrupt output when a JS file with `experimentalEmbeddedSnippetsEnabled` contains non-embedded template literals alongside embedded ones (e.g. `console.log(\`test\`)` next to `graphql(\`...\`)`). diff --git a/.changeset/fix-tailwind-utility-slash.md b/.changeset/fix-tailwind-utility-slash.md new file mode 100644 index 000000000000..4723d6a91747 --- /dev/null +++ b/.changeset/fix-tailwind-utility-slash.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8897](https://github.com/biomejs/biome/issues/8897): Biome now parses `@utility` names containing `/` when Tailwind directives are enabled. diff --git a/.changeset/fix-use-semantic-elements-base-concepts.md b/.changeset/fix-use-semantic-elements-base-concepts.md new file mode 100644 index 000000000000..b840878c8735 --- /dev/null +++ b/.changeset/fix-use-semantic-elements-base-concepts.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9245](https://github.com/biomejs/biome/issues/9245): the [`useSemanticElements`](https://biomejs.dev/linter/rules/use-semantic-elements/) rule no longer suggests `` for `role="status"` and `role="alert"`. The `` element is only a `relatedConcept` of these roles, not a direct semantic equivalent. These roles are now excluded from suggestions, aligning with the intended behavior of the upstream `prefer-tag-over-role` rule. diff --git a/.changeset/flat-beers-battle.md b/.changeset/flat-beers-battle.md new file mode 100644 index 000000000000..3e65d504f931 --- /dev/null +++ b/.changeset/flat-beers-battle.md @@ -0,0 +1,31 @@ +--- +"@biomejs/biome": minor +--- + +Added the `sortBareImports` option to [`organizeImports`](https://biomejs.dev/assist/actions/organize-imports/), +which allows bare imports to be sorted within other imports when set to `false`. + +```json +{ + "assist": { + "actions": { + "source": { + "organizeImports": { + "level": "on", + "options": { "sortBareImports": true } + } + } + } + } +} +``` + +```diff +- import "b"; + import "a"; ++ import "b"; + import { A } from "a"; ++ import "./file"; + import { Local } from "./file"; +- import "./file"; +``` diff --git a/.changeset/lovely-clouds-change.md b/.changeset/lovely-clouds-change.md new file mode 100644 index 000000000000..84893a80e446 --- /dev/null +++ b/.changeset/lovely-clouds-change.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#9433](https://github.com/biomejs/biome/issues/9433): [`noBlankTarget`](https://biomejs.dev/linter/rules/no-blank-target/) now correctly handles dynamic href attributes, such as ``. diff --git a/.changeset/plugin-file-scoping.md b/.changeset/plugin-file-scoping.md new file mode 100644 index 000000000000..046ee96372b2 --- /dev/null +++ b/.changeset/plugin-file-scoping.md @@ -0,0 +1,14 @@ +--- +"@biomejs/biome": minor +--- + +Added `includes` option for plugin file scoping. Plugins can now be configured with glob patterns to restrict which files they run on. Use negated globs for exclusions. + +```json +{ + "plugins": [ + "global-plugin.grit", + { "path": "scoped-plugin.grit", "includes": ["src/**/*.ts", "!**/*.test.ts"] } + ] +} +``` diff --git a/.changeset/resolve-record-type.md b/.changeset/resolve-record-type.md new file mode 100644 index 000000000000..252c1aea15a8 --- /dev/null +++ b/.changeset/resolve-record-type.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#6606](https://github.com/biomejs/biome/issues/6606): The type inference engine now resolves `Record` types, synthesizing them as object types with index signatures. This improves accuracy for type-aware lint rules such as `noFloatingPromises`, `noMisusedPromises`, `useAwaitThenable`, and `useArraySortCompare` when operating on Record-typed values. diff --git a/.claude/skills/README.md b/.claude/skills/README.md index 513ffc201808..3f22d29b51e5 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 dc9c47c878a2..fb8470b8a7ca 100644 --- a/.claude/skills/biome-developer/SKILL.md +++ b/.claude/skills/biome-developer/SKILL.md @@ -14,6 +14,50 @@ 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 @@ -23,6 +67,7 @@ This skill provides general development best practices, common gotchas, and Biom - 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:** - Do NOT build the full Biome binary just to inspect syntax (expensive) - use parser crate's `quick_test` instead @@ -44,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 `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:** -- Do NOT 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 @@ -67,6 +160,56 @@ 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:** @@ -208,10 +351,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 diff --git a/.claude/skills/diagnostics-development/SKILL.md b/.claude/skills/diagnostics-development/SKILL.md index be7be2df5d93..0a35072626b6 100644 --- a/.claude/skills/diagnostics-development/SKILL.md +++ b/.claude/skills/diagnostics-development/SKILL.md @@ -26,6 +26,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 diff --git a/.claude/skills/formatter-development/SKILL.md b/.claude/skills/formatter-development/SKILL.md index f19ab144435b..b2c215a66fc2 100644 --- a/.claude/skills/formatter-development/SKILL.md +++ b/.claude/skills/formatter-development/SKILL.md @@ -14,6 +14,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 1199fa5d86d6..4d89217cb58f 100644 --- a/.claude/skills/lint-rule-development/SKILL.md +++ b/.claude/skills/lint-rule-development/SKILL.md @@ -14,6 +14,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 @@ -75,18 +88,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: @@ -188,6 +241,40 @@ 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 **Every test file must start with a top-level comment** declaring whether it expects diagnostics. The test runner enforces this — see the `testing-codegen` skill for full rules. The short version: `valid.js` — comment is **mandatory** (test panics without it): @@ -204,6 +291,13 @@ 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 diff --git a/.claude/skills/parser-development/SKILL.md b/.claude/skills/parser-development/SKILL.md index 5bcad9d9748b..25f65ad5dd90 100644 --- a/.claude/skills/parser-development/SKILL.md +++ b/.claude/skills/parser-development/SKILL.md @@ -14,6 +14,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 dbb89fd4a026..f76b6350cf80 100644 --- a/.claude/skills/prettier-compare/SKILL.md +++ b/.claude/skills/prettier-compare/SKILL.md @@ -14,6 +14,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/testing-codegen/SKILL.md b/.claude/skills/testing-codegen/SKILL.md index 4795df94ecaf..15f0b1d9c6dc 100644 --- a/.claude/skills/testing-codegen/SKILL.md +++ b/.claude/skills/testing-codegen/SKILL.md @@ -15,6 +15,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 b053ef4edcd7..4f13444b293d 100644 --- a/.claude/skills/type-inference/SKILL.md +++ b/.claude/skills/type-inference/SKILL.md @@ -14,6 +14,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 @@ -302,6 +314,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/noUndeclaredClasses", fs.cli_path()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_undeclared_classes_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/noUndeclaredClasses", fs.cli_path()].as_slice()), + ); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "no_undeclared_classes_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