@hyeon/linter
v11.0.4
Published
Biome-based lint, format, and import organize configuration
Maintainers
Readme
@hyeon/linter
패키지 매니저에 맞춰 설치하세요.
npm install --save-dev @hyeon/linter @biomejs/biome
pnpm add -D @hyeon/linter @biomejs/biome
yarn add -D @hyeon/linter @biomejs/biome
bun add -d @hyeon/linter @biomejs/biomeAI 셋업 프롬프트
아래 코드블럭을 복사해서 AI에게 붙여넣으세요.
@hyeon/linter를 이 프로젝트에 설치해줘.
설치:
프로젝트의 `packageManager`를 확인하고 맞는 명령을 사용해 설치해줘. `packageManager`가 없으면 npm을 사용해줘.
```bash
npm install --save-dev @hyeon/linter @biomejs/biome
pnpm add -D @hyeon/linter @biomejs/biome
yarn add -D @hyeon/linter @biomejs/biome
bun add -d @hyeon/linter @biomejs/biome
```
프로젝트 루트에 biome.jsonc 파일을 생성하고 아래 내용을 넣어줘:
```jsonc
{
"extends": ["@hyeon/linter/biome-config"]
}
```
package.json scripts에 아래를 추가해줘:
```json
{
"lint": "biome ci .",
"lint:fix": "biome check --write .",
"lint:fix:unsafe": "biome check --write --unsafe ."
}
```
.vscode/settings.json에 아래를 추가해줘:
```json
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"biome.enabled": true,
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
```
VS Code Biome 확장에서 `Request textDocument/codeAction failed` 또는 `failed to access range` 오류가 발생하면 `editor.codeActionsOnSave` 블록만 제거하고 `biome check --write .` 또는 `lint:fix` 스크립트로 import 정리와 fix를 수동 적용해줘. 이 오류는 설정 파일보다 Biome VS Code/LSP의 저장 시 codeAction 경로에서 발생하는 경우가 많아 formatter만 저장 시 적용하는 구성이 더 안정적이야.
.vscode/extensions.json에 아래를 추가해줘:
```json
{
"recommendations": ["biomejs.biome"]
}
```
설치 후 biome check --write . 로 전체 포맷을 적용해줘.
`useSortedClasses` Tailwind class 정렬은 저장 시 `source.fixAll.biome` 또는 biome check --write . 로 적용돼. `useOptionalChain`처럼 Biome이 unsafe fix로 분류한 수정까지 적용해야 하면 biome check --write --unsafe . 를 별도로 실행해줘.지원 정책
- TypeScript 프로젝트는 TypeScript 5.9+ 기준
- Biome 기반 (lint + format + import organize 단일 도구)
- ESLint/Prettier 레거시 경로는 v11에서 완전 제거됨
- Tailwind CSS v4의
@theme,@utility같은 CSS 지시문은 기본 설정에서 파싱 가능 - Tailwind CSS class 정렬은
useSortedClasses를 기본error로 활성화하며,clsx/cva/cn/tw계열 함수 문자열도 검사 - Tailwind class 정렬은
@hyeon/linter에서 safe fix로 재분류하므로 VS Codesource.fixAll.biome저장 시 자동 적용 가능
왜 Biome인가
도구별 속도 벤치마크
출처: oxc-project/bench-linter (VS Code 코드베이스, M2 Max 12코어)
| 도구 | Lint 시간 | vs ESLint | Format 시간 | vs Prettier | |---|---|---|---|---| | ESLint + Prettier | 31s | 1x | 1x | 1x | | oxlint + oxfmt | 139ms | 62x 빠름 | 30x 빠름 | | Biome | 377ms | ~20x 빠름 | 10x 빠름 |
- 셋 다 Rust 기반 멀티스레드(rayon) — 파일 수 증가 시 코어 수에 비례해 스케일
- ESLint만 싱글스레드(Node.js) — 파일 수에 선형 비례해 느려짐
- oxlint이 Biome보다 ~2.5x 빠르지만, 둘 다 1초 미만이라 체감 차이 없음
룰 커버리지
| 도구 | 총 룰 수 | 타입 기반 분석 | auto-fix | 플러그인 생태계 | |---|---|---|---|---| | ESLint | 700+ (+4000 플러그인) | ✅ 완전 | ✅ 우수 | 4000+ | | Biome v2 | 423+ | ⚠️ 일부 프로젝트 기반 분석 | ✅ 양호 | 성장 중 | | oxlint | ~300 | ✅ tsgolint 43룰 | ⚠️ 제한적 | 최소 |
import DX 커버리지 — Biome 선택 이유
| 기능 | ESLint 레거시 | oxlint+oxfmt | Biome |
|---|:---:|:---:|:---:|
| import 그룹 정렬 | ✅ | ✅ | ✅ |
| specifier 정렬 { a, B, c } | ✅ | ❌ | ✅ |
| 중복 import 병합 | ✅ | ❌ | ✅ 자동 merge |
| import 최상단 강제 | ✅ | ❌ | ✅ chunk 기반 |
| import 뒤 빈 줄 | ✅ | ❌ | ✅ :BLANK_LINE: |
| consistent-type-imports | ✅ | ❌ | ✅ useImportType |
| 그룹 간 빈 줄 | ✅ | ⚠️ bool만 | ✅ :BLANK_LINE: |
| side-effect import 처리 | 수동 | 무시 | ✅ auto chunk |
| custom group (glob) | ❌ | 일부 | ✅ glob + ! 예외 |
ESLint → Biome 규칙 마이그레이션 커버리지
기존 @hyeon/linter v10에서 사용하던 ESLint 규칙을 Biome에서 어떻게 커버하는지 전수 매핑입니다.
@eslint/js recommended (base.mjs)
@eslint/jsconfigs.recommended에 포함된 규칙들. Biome은"recommended": true로 대부분 활성화됩니다.
| ESLint 규칙 | Biome 규칙 | 상태 |
|---|---|:---:|
| constructor-super | noInvalidConstructorSuper | ✅ |
| for-direction | useValidForDirection | ✅ |
| getter-return | useGetterReturn | ✅ |
| no-async-promise-executor | noAsyncPromiseExecutor | ✅ |
| no-case-declarations | noSwitchDeclarations | ✅ |
| no-class-assign | noClassAssign | ✅ |
| no-compare-neg-zero | noCompareNegZero | ✅ |
| no-cond-assign | noAssignInExpressions | ✅ |
| no-const-assign | noConstAssign | ✅ |
| no-constant-condition | noConstantCondition | ✅ |
| no-control-regex | noControlCharactersInRegex | ✅ |
| no-debugger | noDebugger | ✅ |
| no-delete-var | (TS 환경에서 불필요) | ➖ |
| no-dupe-args | noDuplicateParameters | ✅ |
| no-dupe-class-members | noDuplicateClassMembers | ✅ |
| no-dupe-else-if | noDuplicateElseIf | ✅ |
| no-dupe-keys | noDuplicateObjectKeys | ✅ |
| no-duplicate-case | noDuplicateCase | ✅ |
| no-empty | noEmptyBlockStatements | ✅ |
| no-empty-character-class | noEmptyCharacterClassInRegex | ✅ |
| no-empty-pattern | noEmptyPattern | ✅ |
| no-ex-assign | noCatchAssign | ✅ |
| no-extra-boolean-cast | noExtraBooleanCast | ✅ |
| no-fallthrough | noFallthroughSwitchClause | ✅ |
| no-func-assign | noFunctionAssign | ✅ |
| no-global-assign | noGlobalAssign | ✅ |
| no-import-assign | noImportAssign | ✅ |
| no-inner-declarations | noInnerDeclarations | ✅ |
| no-irregular-whitespace | noIrregularWhitespace | ✅ |
| no-loss-of-precision | noPrecisionLoss | ✅ |
| no-misleading-character-class | noMisleadingCharacterClass | ✅ |
| no-new-native-nonconstructor | noInvalidBuiltinInstantiation | ✅ |
| no-nonoctal-decimal-escape | noNonoctalDecimalEscape | ✅ |
| no-obj-calls | noGlobalObjectCalls | ✅ |
| no-prototype-builtins | noPrototypeBuiltins | ✅ |
| no-redeclare | noRedeclare | ✅ |
| no-regex-spaces | noMultipleSpacesInRegex | ✅ |
| no-self-assign | noSelfAssign | ✅ |
| no-setter-return | noSetterReturn | ✅ |
| no-shadow-restricted-names | noShadowRestrictedNames | ✅ |
| no-sparse-arrays | noSparseArray | ✅ |
| no-this-before-super | noUnreachableSuper | ✅ |
| no-undef | noUndeclaredVariables | ✅ |
| no-unreachable | noUnreachable | ✅ |
| no-unsafe-finally | noUnsafeFinally | ✅ |
| no-unsafe-negation | noUnsafeNegation | ✅ |
| no-unsafe-optional-chaining | noUnsafeOptionalChaining | ✅ |
| no-unused-labels | noUnusedLabels | ✅ |
| no-unused-private-class-members | noUnusedPrivateClassMembers | ✅ |
| no-unused-vars | noUnusedVariables | ✅ |
| no-useless-catch | noUselessCatch | ✅ |
| no-useless-escape | noUselessEscapeInRegex | ✅ |
| no-with | noWith | ✅ |
| require-yield | useYield | ✅ |
| use-isnan | useIsNan | ✅ |
| valid-typeof | useValidTypeof | ✅ |
typescript-eslint recommended (typescript.mjs)
| ESLint 규칙 | Biome 규칙 | 상태 | 비고 |
|---|---|:---:|---|
| @ts/no-explicit-any | noExplicitAny | ✅ | off으로 설정 |
| @ts/no-unused-vars | noUnusedVariables + noUnusedFunctionParameters | ✅ | _ 접두사 패턴 지원 |
| @ts/consistent-type-imports | useImportType | ✅ | inlineType 스타일 지원 |
| @ts/explicit-function-return-type | useExplicitType | ✅ | nursery 규칙이라 기본 미활성화 |
| @ts/explicit-module-boundary-types | useExplicitType | ✅ | nursery 규칙이라 기본 미활성화 |
| @ts/no-non-null-assertion | noNonNullAssertion | ✅ | off으로 설정 |
| @ts/no-empty-interface | noEmptyInterface | ✅ | |
| @ts/no-empty-object-type | noBannedTypes | ✅ | |
| @ts/ban-types | noBannedTypes | ✅ | |
| @ts/no-inferrable-types | noInferrableTypes | ✅ | |
| @ts/no-namespace | noNamespace | ✅ | |
| @ts/no-unnecessary-type-constraint | noUselessTypeConstraint | ✅ | |
| @ts/prefer-as-const | useAsConstAssertion | ✅ | |
eslint-plugin-react + react-refresh (react.mjs)
| ESLint 규칙 | Biome 규칙 | 상태 | 비고 |
|---|---|:---:|---|
| react/jsx-key | useJsxKeyInIterable | ✅ | |
| react/jsx-no-comment-textnodes | noCommentText | ✅ | |
| react/jsx-no-duplicate-props | noDuplicateJsxProps | ✅ | |
| react/jsx-no-target-blank | noBlankTarget | ✅ | |
| react/jsx-no-useless-fragment | noUselessFragments | ✅ | |
| react/no-children-prop | noChildrenProp | ✅ | |
| react/no-danger-with-children | noDangerouslySetInnerHtmlWithChildren | ✅ | |
| react/void-dom-elements-no-children | noVoidElementsWithChildren | ✅ | |
| react-hooks/rules-of-hooks | useHookAtTopLevel | ✅ | |
| react-hooks/exhaustive-deps | useExhaustiveDependencies | ✅ | |
| react-refresh/only-export-components | useComponentExportOnlyModules | ✅ | |
| react/jsx-curly-brace-presence | useConsistentCurlyBraces | ✅ | 문자열 JSX props/children은 중괄호 없이 작성 |
| react/display-name | — | ❌ | Biome 미구현 |
| react/no-unescaped-entities | — | ❌ | Biome 미구현 |
Prettier + 스타일 규칙 (prettier.mjs)
| ESLint 규칙 | Biome 처리 | 상태 | 비고 |
|---|---|:---:|---|
| prettier/prettier (포맷 전체) | Biome formatter | ✅ | 단일 도구로 통합 |
| ↳ trailingComma: all | trailingCommas: "all" | ✅ | |
| ↳ singleQuote: true | quoteStyle: "single" | ✅ | |
| ↳ semi: false | semicolons: "asNeeded" | ✅ | |
| ↳ tabWidth: 2 | indentWidth: 2 | ✅ | |
| ↳ printWidth: 120 | lineWidth: 120 | ✅ | |
| ↳ arrowParens: always | arrowParentheses: "always" | ✅ | |
| ↳ jsxSingleQuote: false | jsxQuoteStyle: "double" | ✅ | |
| ↳ @trivago/sort-imports | organizeImports | ✅ | 그룹/정렬/병합 모두 지원 |
| ↳ prettier-plugin-tailwindcss | useSortedClasses | ⚠️ | Biome 내장 규칙이지만 unsafe fix라 기본 미활성화 |
| arrow-body-style | — | ❌ | Biome 미구현 |
| prefer-arrow-callback | useArrowFunction | ✅ | |
| curly | useBlockStatements | ✅ | |
| lines-around-comment | — | ❌ | formatter 영역 |
| no-confusing-arrow | — | ❌ | Biome 미구현 |
| no-mixed-operators | — | ❌ | Biome 미구현 |
| no-tabs | formatter indentStyle: "space" | ✅ | |
| quotes | formatter quoteStyle: "single" | ✅ | |
eslint-plugin-import-x + 커스텀 (hansanghyeon.mjs)
| ESLint 규칙 | Biome 처리 | 상태 | 비고 |
|---|---|:---:|---|
| import-x/first | organizeImports chunk | ✅ | |
| import-x/newline-after-import | organizeImports :BLANK_LINE: | ✅ | |
| import-x/no-duplicates | organizeImports 자동 merge | ✅ | |
| import-x/no-unresolved | — | ➖ | 원래 off |
| no-empty-pattern | noEmptyPattern | ✅ | off으로 설정 |
| camelcase | useNamingConvention | ✅ | 기본 미활성화 |
| class-methods-use-this | — | ❌ | Biome 미구현 |
| no-multi-spaces | formatter | ✅ | |
마이그레이션 커버리지 요약
| 카테고리 | 전체 규칙 | ✅ 커버 | ❌ 미커버 | ➖ 해당없음 |
|---|---|---|---|---|
| @eslint/js recommended | 52 | 50 | 0 | 2 |
| typescript-eslint | 13 | 13 | 0 | 0 |
| eslint-plugin-react + hooks + refresh | 13 | 11 | 2 | 0 |
| Prettier + 스타일 | 18 | 14 | 4 | 0 |
| import-x + 커스텀 | 8 | 6 | 1 | 1 |
| 합계 | 104 | 94 (90%) | 7 (7%) | 3 (3%) |
미커버 7개: react/display-name, react/no-unescaped-entities, arrow-body-style, lines-around-comment, no-confusing-arrow, no-mixed-operators, class-methods-use-this — 대부분 스타일 취향 규칙이며 실질적 버그 감지에는 영향 없음.
요약 매트릭스
| | 속도 | 룰 커버리지 | import DX | 단일 도구 | auto-fix | |---|:---:|:---:|:---:|:---:|:---:| | oxlint+oxfmt | ⭐⭐⭐ | ⭐⭐ | ⭐ | ❌ 2개 | ⚠️ | | Biome | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ✅ 1개 | ✅ | | ESLint+Prettier | ⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ❌ 2개+ | ✅ |
프로젝트별 셋업 가이드
Biome은 파일 확장자로 언어를 자동 감지합니다.
.ts,.tsx파일은 TS/React 규칙이 자동 적용됩니다.@hyeon/linter/biome-config를extends로 상속하면 포맷+린트+import 정리 설정이 한 번에 적용됩니다.
공통 설치
npm install --save-dev @hyeon/linter @biomejs/biome
pnpm add -D @hyeon/linter @biomejs/biome
yarn add -D @hyeon/linter @biomejs/biome
bun add -d @hyeon/linter @biomejs/biome공통 scripts
{
"scripts": {
"lint": "biome ci .",
"lint:fix": "biome check --write .",
"lint:fix:unsafe": "biome check --write --unsafe ."
}
}lint:fix는 safe fix와 formatter를 적용합니다. useOptionalChain 같은 unsafe fix까지 적용해야 할 때만 lint:fix:unsafe를 수동으로 실행하세요.
1) JavaScript 프로젝트
// biome.jsonc
{
"extends": ["@hyeon/linter/biome-config"]
}이것만으로 JS recommended 린트 + 포맷 + import 정리가 활성화됩니다.
2) TypeScript 프로젝트
// biome.jsonc
{
"extends": ["@hyeon/linter/biome-config"]
}.ts 파일에 대해 TypeScript 규칙(useImportType, noExplicitAny 등)이 자동 적용됩니다.
추가 커스터마이징:
{
"extends": ["@hyeon/linter/biome-config"],
"linter": {
"rules": {
"suspicious": {
// any를 엄격히 금지하고 싶은 경우
"noExplicitAny": "error"
}
}
}
}3) React 프로젝트
// biome.jsonc
{
"extends": ["@hyeon/linter/biome-config"]
}.tsx 파일에 대해 React 규칙(useJsxKeyInIterable, useHookAtTopLevel, useExhaustiveDependencies 등)이 자동 적용됩니다.
별도 React 프리셋 설치가 필요 없습니다.
4) TypeScript + React (풀셋)
// biome.jsonc
{
"extends": ["@hyeon/linter/biome-config"]
}TS + React 규칙이 모두 자동 적용됩니다. ESLint에서는 5개 프리셋을 조합해야 했지만 Biome은 1줄입니다.
5) Next.js 프로젝트
// biome.jsonc
{
"extends": ["@hyeon/linter/biome-config"],
"linter": {
"rules": {
"performance": {
// Next.js 전용 이미지 최적화 규칙 활성화
"noImgElement": "error"
},
"style": {
// Next.js 전용 head 요소 규칙 활성화
"noHeadElement": "error"
}
}
},
"overrides": [
{
"includes": [".next/**"],
"linter": { "enabled": false },
"formatter": { "enabled": false }
}
]
}6) NestJS / 백엔드 프로젝트
// biome.jsonc
{
"extends": ["@hyeon/linter/biome-config"],
"linter": {
"rules": {
"complexity": {
// NestJS 데코레이터 패턴에서 빈 클래스 허용
"noUselessConstructor": "off"
}
}
},
"overrides": [
{
"includes": ["dist/**"],
"linter": { "enabled": false },
"formatter": { "enabled": false }
}
]
}7) 모노레포
루트에 biome.jsonc를 두고, 각 패키지에서 상속합니다:
monorepo/
├── biome.jsonc # extends @hyeon/linter/biome-config
├── packages/
│ ├── web/
│ │ └── biome.jsonc # extends ../../biome.jsonc + Next.js 오버라이드
│ └── api/
│ └── biome.jsonc # extends ../../biome.jsonc + NestJS 오버라이드// packages/web/biome.jsonc
{
"extends": ["../../biome.jsonc"],
"linter": {
"rules": {
"performance": {
"noImgElement": "error"
}
}
}
}ESLint 프리셋 대응표
| 기존 ESLint 프리셋 | Biome 설정 |
|---|---|
| @hyeon/linter/recommended | "extends": ["@hyeon/linter/biome-config"] (기본 포함) |
| @hyeon/linter/typescript | 자동 적용 (.ts 파일 감지) |
| @hyeon/linter/react | 자동 적용 (.tsx 파일 감지) |
| @hyeon/linter/prettier | 기본 formatter로 통합 |
| @hyeon/linter/hansanghyeon | 기본 설정에 통합 (noExplicitAny: off 등) |
ESLint에서 5개 프리셋을 조합하던 것이 Biome에서는 extends 1줄로 끝납니다.
실행
# lint + format + import 정리 한 번에
npx biome check --write .
# unsafe fix까지 적용해야 할 때만 수동 실행
npx biome check --write --unsafe .
# CI (검사만, 수정 없음)
npx biome ci .v10 → v11 마이그레이션
v11은 Breaking Change입니다. ESLint/Prettier를 Biome으로 완전 교체합니다.
마이그레이션 방법
eslint.config.mjs삭제biome.jsonc생성 또는@hyeon/linter/biome-config상속eslint,prettier및 관련 플러그인 devDependencies 제거@biomejs/biome설치- lint 스크립트를
biome check또는biome ci로 변경
