chore(lint): adopt biome through the ultracite preset
Biome replaces the eslint + prettier pair outright: one binary, one pass, and it is fast enough to run on every save and every commit without anyone noticing. Ultracite is the preset — zero-config, strict, and mostly auto-fixable — so biome.jsonc only carries what this repo overrides on top of it.
This commit is contained in:
@@ -0,0 +1,123 @@
|
|||||||
|
# Ultracite Code Standards
|
||||||
|
|
||||||
|
This project uses **Ultracite**, a zero-config preset that enforces strict code quality standards through automated formatting and linting.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
- **Format code**: `pnpm dlx ultracite fix`
|
||||||
|
- **Check for issues**: `pnpm dlx ultracite check`
|
||||||
|
- **Diagnose setup**: `pnpm dlx ultracite doctor`
|
||||||
|
|
||||||
|
Biome (the underlying engine) provides robust linting and formatting. Most issues are automatically fixable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Principles
|
||||||
|
|
||||||
|
Write code that is **accessible, performant, type-safe, and maintainable**. Focus on clarity and explicit intent over brevity.
|
||||||
|
|
||||||
|
### Type Safety & Explicitness
|
||||||
|
|
||||||
|
- Use explicit types for function parameters and return values when they enhance clarity
|
||||||
|
- Prefer `unknown` over `any` when the type is genuinely unknown
|
||||||
|
- Use const assertions (`as const`) for immutable values and literal types
|
||||||
|
- Leverage TypeScript's type narrowing instead of type assertions
|
||||||
|
- Use meaningful variable names instead of magic numbers - extract constants with descriptive names
|
||||||
|
|
||||||
|
### Modern JavaScript/TypeScript
|
||||||
|
|
||||||
|
- Use arrow functions for callbacks and short functions
|
||||||
|
- Prefer `for...of` loops over `.forEach()` and indexed `for` loops
|
||||||
|
- Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access
|
||||||
|
- Prefer template literals over string concatenation
|
||||||
|
- Use destructuring for object and array assignments
|
||||||
|
- Use `const` by default, `let` only when reassignment is needed, never `var`
|
||||||
|
|
||||||
|
### Async & Promises
|
||||||
|
|
||||||
|
- Always `await` promises in async functions - don't forget to use the return value
|
||||||
|
- Use `async/await` syntax instead of promise chains for better readability
|
||||||
|
- Handle errors appropriately in async code with try-catch blocks
|
||||||
|
- Don't use async functions as Promise executors
|
||||||
|
|
||||||
|
### React & JSX
|
||||||
|
|
||||||
|
- Use function components over class components
|
||||||
|
- Call hooks at the top level only, never conditionally
|
||||||
|
- Specify all dependencies in hook dependency arrays correctly
|
||||||
|
- Use the `key` prop for elements in iterables (prefer unique IDs over array indices)
|
||||||
|
- Nest children between opening and closing tags instead of passing as props
|
||||||
|
- Don't define components inside other components
|
||||||
|
- Use semantic HTML and ARIA attributes for accessibility:
|
||||||
|
- Provide meaningful alt text for images
|
||||||
|
- Use proper heading hierarchy
|
||||||
|
- Add labels for form inputs
|
||||||
|
- Include keyboard event handlers alongside mouse events
|
||||||
|
- Use semantic elements (`<button>`, `<nav>`, etc.) instead of divs with roles
|
||||||
|
|
||||||
|
### Error Handling & Debugging
|
||||||
|
|
||||||
|
- Remove `console.log`, `debugger`, and `alert` statements from production code
|
||||||
|
- Throw `Error` objects with descriptive messages, not strings or other values
|
||||||
|
- Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them
|
||||||
|
- Prefer early returns over nested conditionals for error cases
|
||||||
|
|
||||||
|
### Code Organization
|
||||||
|
|
||||||
|
- Keep functions focused and under reasonable cognitive complexity limits
|
||||||
|
- Extract complex conditions into well-named boolean variables
|
||||||
|
- Use early returns to reduce nesting
|
||||||
|
- Prefer simple conditionals over nested ternary operators
|
||||||
|
- Group related code together and separate concerns
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Add `rel="noopener"` when using `target="_blank"` on links
|
||||||
|
- Avoid `dangerouslySetInnerHTML` unless absolutely necessary
|
||||||
|
- Don't use `eval()` or assign directly to `document.cookie`
|
||||||
|
- Validate and sanitize user input
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Avoid spread syntax in accumulators within loops
|
||||||
|
- Use top-level regex literals instead of creating them in loops
|
||||||
|
- Prefer specific imports over namespace imports
|
||||||
|
- Avoid barrel files (index files that re-export everything)
|
||||||
|
- Use proper image components (e.g., Next.js `<Image>`) over `<img>` tags
|
||||||
|
|
||||||
|
### Framework-Specific Guidance
|
||||||
|
|
||||||
|
**Next.js:**
|
||||||
|
- Use Next.js `<Image>` component for images
|
||||||
|
- Use `next/head` or App Router metadata API for head elements
|
||||||
|
- Use Server Components for async data fetching instead of async Client Components
|
||||||
|
|
||||||
|
**React 19+:**
|
||||||
|
- Use ref as a prop instead of `React.forwardRef`
|
||||||
|
|
||||||
|
**Solid/Svelte/Vue/Qwik:**
|
||||||
|
- Use `class` and `for` attributes (not `className` or `htmlFor`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Write assertions inside `it()` or `test()` blocks
|
||||||
|
- Avoid done callbacks in async tests - use async/await instead
|
||||||
|
- Don't use `.only` or `.skip` in committed code
|
||||||
|
- Keep test suites reasonably flat - avoid excessive `describe` nesting
|
||||||
|
|
||||||
|
## When Biome Can't Help
|
||||||
|
|
||||||
|
Biome's linter will catch most issues automatically. Focus your attention on:
|
||||||
|
|
||||||
|
1. **Business logic correctness** - Biome can't validate your algorithms
|
||||||
|
2. **Meaningful naming** - Use descriptive names for functions, variables, and types
|
||||||
|
3. **Architecture decisions** - Component structure, data flow, and API design
|
||||||
|
4. **Edge cases** - Handle boundary conditions and error states
|
||||||
|
5. **User experience** - Accessibility, performance, and usability considerations
|
||||||
|
6. **Documentation** - Add comments for complex logic, but prefer self-documenting code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Most formatting and common issues are automatically fixed by Biome. Run `pnpm dlx ultracite fix` before committing to ensure compliance.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"command": "pnpm run fix --skip=correctness/noUnusedImports",
|
||||||
|
"type": "command"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"matcher": "Write|Edit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["biomejs.biome"]
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
||||||
|
"extends": [
|
||||||
|
"ultracite/biome/core",
|
||||||
|
"ultracite/biome/vitest",
|
||||||
|
"ultracite/biome/react",
|
||||||
|
"ultracite/biome/next",
|
||||||
|
"ultracite/biome/tanstack"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
// An imported UI icon set, vendored verbatim. These are build inputs for
|
||||||
|
// scripts/gen.mjs, not authored source: formatting them churns 507 files
|
||||||
|
// and reorders SVG attributes for no benefit, and `noSvgWithoutTitle` does
|
||||||
|
// not apply to a corpus that only ever reaches the DOM through the
|
||||||
|
// generator.
|
||||||
|
"includes": [
|
||||||
|
"!packages/editor-icons/assets",
|
||||||
|
// Static assets apps/web serves from /public — the wallpapers, the dock
|
||||||
|
// glyphs, the favicon. Hand-drawn SVGs, never parsed as JSX: they reach
|
||||||
|
// the page through an <img src> or a CSS url(), so `noSvgWithoutTitle` is
|
||||||
|
// checking a file that has no accessible name to give. The call site
|
||||||
|
// supplies the alt text instead.
|
||||||
|
"!**/apps/*/public"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"suspicious": {
|
||||||
|
// Unreliable here, in two ways that between them cover all 54 hits it
|
||||||
|
// reported, and every one of its "fixes" deletes a live guard:
|
||||||
|
//
|
||||||
|
// - It reads `a?.b` as non-nullish even when `a` is nullable, so it
|
||||||
|
// calls the fallback in `options.width ?? preset?.width ?? 1440`
|
||||||
|
// (`presetById` returns `DevicePreset | null`) unreachable. It is
|
||||||
|
// reached whenever the id matches no preset.
|
||||||
|
// - It does not model class fields mutated from another method, so
|
||||||
|
// `private replaying = false` reads as permanently false at its
|
||||||
|
// `if (this.replaying)` guard — the flag that stops undo/redo from
|
||||||
|
// re-recording its own replays. Same for `awaiting` and `editing`.
|
||||||
|
//
|
||||||
|
// Revisit when the rule leaves nursery; the genuine hits it also found
|
||||||
|
// were redundant `?? ""` on non-optional strings, which cost nothing.
|
||||||
|
"noUnnecessaryConditions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
// Each package's `src/index.ts` *is* its published surface — it is what
|
||||||
|
// the `exports` map in package.json points at. `dnd/manager.ts` is the
|
||||||
|
// same idea internally: one wrapper that owns the dnd-kit dependency so
|
||||||
|
// nothing else imports it directly. `noBarrelFile` is aimed at re-export
|
||||||
|
// hubs that defeat tree-shaking inside an app, not at a declared API.
|
||||||
|
//
|
||||||
|
// The `**/` prefix is now redundant — every package lives at
|
||||||
|
// packages/*/ under this one workspace root — but it is harmless and
|
||||||
|
// matches the same set, so it stays rather than churning the glob.
|
||||||
|
"includes": [
|
||||||
|
"**/packages/*/src/index.ts",
|
||||||
|
"packages/overlay/src/dnd/manager.ts"
|
||||||
|
],
|
||||||
|
"linter": { "rules": { "performance": { "noBarrelFile": "off" } } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// `ultracite/biome/next` is extended at the root for the packages that do
|
||||||
|
// target Next. apps/web does not: it is a TanStack Start app, where
|
||||||
|
// `<img>` is the image element and the `<head>` in the root route's shell
|
||||||
|
// component is the document head it is supposed to render. Both rules
|
||||||
|
// exist to push you toward `next/image` and the metadata API, neither of
|
||||||
|
// which exists here — following them would mean importing from a
|
||||||
|
// framework this app does not depend on.
|
||||||
|
"includes": ["apps/web/**"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"performance": { "noImgElement": "off" },
|
||||||
|
"style": { "noHeadElement": "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Generated by the TanStack Router CLI on every dev run and every build.
|
||||||
|
// It ships its own @ts-nocheck and lint-disable banner and is rewritten
|
||||||
|
// from src/routes, so formatting it is churn that the next `tsr generate`
|
||||||
|
// discards.
|
||||||
|
"includes": ["**/src/routeTree.gen.ts"],
|
||||||
|
"formatter": { "enabled": false },
|
||||||
|
"linter": { "enabled": false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user