@hyeon/linter 10.0.1-dev.1.29 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,420 @@
1
- ## Installation
1
+ # @hyeon/linter
2
2
 
3
3
  ```bash
4
- npm install --save-dev @hyeon/linter
4
+ npm install --save-dev @hyeon/linter @biomejs/biome
5
5
  ```
6
6
 
7
- ## Usage
7
+ > AI 사용자용: 설치/셋업 가이드는 [`install.md`](./install.md), 사용 예시는 [`USAGE_EXAMPLES.md`](./USAGE_EXAMPLES.md)를 먼저 확인하세요.
8
8
 
9
- ```js
10
- import hyeonLinter from '@hyeon/linter'
9
+ ## 지원 정책
11
10
 
12
- export default [
13
- ...hyeonLinter.react,
14
- ...hyeonLinter.prettier,
15
- ...hyeonLinter.typescript,
16
- ]
11
+ - TypeScript 5.9+ 전용
12
+ - Biome 기반 (lint + format + import organize 단일 도구)
13
+ - ESLint/Prettier 레거시 경로는 v11에서 완전 제거됨
14
+
15
+ ## 왜 Biome인가
16
+
17
+ ### 도구별 속도 벤치마크
18
+
19
+ > 출처: [oxc-project/bench-linter](https://github.com/oxc-project/bench-linter) (VS Code 코드베이스, M2 Max 12코어)
20
+
21
+ | 도구 | Lint 시간 | vs ESLint | Format 시간 | vs Prettier |
22
+ |---|---|---|---|---|
23
+ | **ESLint + Prettier** | 31s | 1x | 1x | 1x |
24
+ | **oxlint + oxfmt** | 139ms | **62x** 빠름 | **30x** 빠름 |
25
+ | **Biome** | 377ms | **~20x** 빠름 | **10x** 빠름 |
26
+
27
+ - 셋 다 **Rust 기반 멀티스레드(rayon)** — 파일 수 증가 시 코어 수에 비례해 스케일
28
+ - ESLint만 **싱글스레드(Node.js)** — 파일 수에 선형 비례해 느려짐
29
+ - oxlint이 Biome보다 ~2.5x 빠르지만, **둘 다 1초 미만**이라 체감 차이 없음
30
+
31
+ ### 룰 커버리지
32
+
33
+ | 도구 | 총 룰 수 | type-aware | auto-fix | 플러그인 생태계 |
34
+ |---|---|---|---|---|
35
+ | **ESLint** | 700+ (+4000 플러그인) | ✅ 완전 | ✅ 우수 | 4000+ |
36
+ | **Biome v2** | 423+ | ✅ v2 추가 | ✅ 양호 | 성장 중 |
37
+ | **oxlint** | ~300 | ✅ tsgolint 43룰 | ⚠️ 제한적 | 최소 |
38
+
39
+ ### import DX 커버리지 — Biome 선택 이유
40
+
41
+ | 기능 | ESLint 레거시 | oxlint+oxfmt | Biome |
42
+ |---|:---:|:---:|:---:|
43
+ | import 그룹 정렬 | ✅ | ✅ | ✅ |
44
+ | specifier 정렬 `{ a, B, c }` | ✅ | ❌ | ✅ |
45
+ | 중복 import 병합 | ✅ | ❌ | ✅ 자동 merge |
46
+ | import 최상단 강제 | ✅ | ❌ | ✅ chunk 기반 |
47
+ | import 뒤 빈 줄 | ✅ | ❌ | ✅ `:BLANK_LINE:` |
48
+ | consistent-type-imports | ✅ | ❌ | ✅ `useImportType` |
49
+ | 그룹 간 빈 줄 | ✅ | ⚠️ bool만 | ✅ `:BLANK_LINE:` |
50
+ | side-effect import 처리 | 수동 | 무시 | ✅ auto chunk |
51
+ | custom group (glob) | ❌ | 일부 | ✅ glob + `!` 예외 |
52
+
53
+ ### ESLint → Biome 규칙 마이그레이션 커버리지
54
+
55
+ 기존 `@hyeon/linter` v10에서 사용하던 ESLint 규칙을 Biome에서 어떻게 커버하는지 전수 매핑입니다.
56
+
57
+ #### `@eslint/js` recommended (base.mjs)
58
+
59
+ > `@eslint/js` configs.recommended에 포함된 규칙들. Biome은 `"recommended": true`로 대부분 활성화됩니다.
60
+
61
+ | ESLint 규칙 | Biome 규칙 | 상태 |
62
+ |---|---|:---:|
63
+ | `constructor-super` | `noInvalidConstructorSuper` | ✅ |
64
+ | `for-direction` | `useValidForDirection` | ✅ |
65
+ | `getter-return` | `useGetterReturn` | ✅ |
66
+ | `no-async-promise-executor` | `noAsyncPromiseExecutor` | ✅ |
67
+ | `no-case-declarations` | `noSwitchDeclarations` | ✅ |
68
+ | `no-class-assign` | `noClassAssign` | ✅ |
69
+ | `no-compare-neg-zero` | `noCompareNegZero` | ✅ |
70
+ | `no-cond-assign` | `noAssignInExpressions` | ✅ |
71
+ | `no-const-assign` | `noConstAssign` | ✅ |
72
+ | `no-constant-condition` | `noConstantCondition` | ✅ |
73
+ | `no-control-regex` | `noControlCharactersInRegex` | ✅ |
74
+ | `no-debugger` | `noDebugger` | ✅ |
75
+ | `no-delete-var` | (TS 환경에서 불필요) | ➖ |
76
+ | `no-dupe-args` | `noDuplicateParameters` | ✅ |
77
+ | `no-dupe-class-members` | `noDuplicateClassMembers` | ✅ |
78
+ | `no-dupe-else-if` | `noDuplicateElseIf` | ✅ |
79
+ | `no-dupe-keys` | `noDuplicateObjectKeys` | ✅ |
80
+ | `no-duplicate-case` | `noDuplicateCase` | ✅ |
81
+ | `no-empty` | `noEmptyBlockStatements` | ✅ |
82
+ | `no-empty-character-class` | `noEmptyCharacterClassInRegex` | ✅ |
83
+ | `no-empty-pattern` | `noEmptyPattern` | ✅ |
84
+ | `no-ex-assign` | `noCatchAssign` | ✅ |
85
+ | `no-extra-boolean-cast` | `noExtraBooleanCast` | ✅ |
86
+ | `no-fallthrough` | `noFallthroughSwitchClause` | ✅ |
87
+ | `no-func-assign` | `noFunctionAssign` | ✅ |
88
+ | `no-global-assign` | `noGlobalAssign` | ✅ |
89
+ | `no-import-assign` | `noImportAssign` | ✅ |
90
+ | `no-inner-declarations` | `noInnerDeclarations` | ✅ |
91
+ | `no-irregular-whitespace` | `noIrregularWhitespace` | ✅ |
92
+ | `no-loss-of-precision` | `noPrecisionLoss` | ✅ |
93
+ | `no-misleading-character-class` | `noMisleadingCharacterClass` | ✅ |
94
+ | `no-new-native-nonconstructor` | `noInvalidBuiltinInstantiation` | ✅ |
95
+ | `no-nonoctal-decimal-escape` | `noNonoctalDecimalEscape` | ✅ |
96
+ | `no-obj-calls` | `noGlobalObjectCalls` | ✅ |
97
+ | `no-prototype-builtins` | `noPrototypeBuiltins` | ✅ |
98
+ | `no-redeclare` | `noRedeclare` | ✅ |
99
+ | `no-regex-spaces` | `noMultipleSpacesInRegex` | ✅ |
100
+ | `no-self-assign` | `noSelfAssign` | ✅ |
101
+ | `no-setter-return` | `noSetterReturn` | ✅ |
102
+ | `no-shadow-restricted-names` | `noShadowRestrictedNames` | ✅ |
103
+ | `no-sparse-arrays` | `noSparseArray` | ✅ |
104
+ | `no-this-before-super` | `noUnreachableSuper` | ✅ |
105
+ | `no-undef` | `noUndeclaredVariables` | ✅ |
106
+ | `no-unreachable` | `noUnreachable` | ✅ |
107
+ | `no-unsafe-finally` | `noUnsafeFinally` | ✅ |
108
+ | `no-unsafe-negation` | `noUnsafeNegation` | ✅ |
109
+ | `no-unsafe-optional-chaining` | `noUnsafeOptionalChaining` | ✅ |
110
+ | `no-unused-labels` | `noUnusedLabels` | ✅ |
111
+ | `no-unused-private-class-members` | `noUnusedPrivateClassMembers` | ✅ |
112
+ | `no-unused-vars` | `noUnusedVariables` | ✅ |
113
+ | `no-useless-catch` | `noUselessCatch` | ✅ |
114
+ | `no-useless-escape` | `noUselessEscapeInRegex` | ✅ |
115
+ | `no-with` | `noWith` | ✅ |
116
+ | `require-yield` | `useYield` | ✅ |
117
+ | `use-isnan` | `useIsNan` | ✅ |
118
+ | `valid-typeof` | `useValidTypeof` | ✅ |
119
+
120
+ #### `typescript-eslint` recommended (typescript.mjs)
121
+
122
+ | ESLint 규칙 | Biome 규칙 | 상태 | 비고 |
123
+ |---|---|:---:|---|
124
+ | `@ts/no-explicit-any` | `noExplicitAny` | ✅ | off으로 설정 |
125
+ | `@ts/no-unused-vars` | `noUnusedVariables` + `noUnusedFunctionParameters` | ✅ | `_` 접두사 패턴 지원 |
126
+ | `@ts/consistent-type-imports` | `useImportType` | ✅ | `inlineType` 스타일 지원 |
127
+ | `@ts/explicit-function-return-type` | `useExplicitType` | ✅ | off으로 설정 |
128
+ | `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ | off으로 설정 |
129
+ | `@ts/no-non-null-assertion` | `noNonNullAssertion` | ✅ | off으로 설정 |
130
+ | `@ts/no-empty-interface` | `noEmptyInterface` | ✅ | |
131
+ | `@ts/no-empty-object-type` | `noBannedTypes` | ✅ | |
132
+ | `@ts/ban-types` | `noBannedTypes` | ✅ | |
133
+ | `@ts/no-inferrable-types` | `noInferrableTypes` | ✅ | |
134
+ | `@ts/no-namespace` | `noNamespace` | ✅ | |
135
+ | `@ts/no-unnecessary-type-constraint` | `noUselessTypeConstraint` | ✅ | |
136
+ | `@ts/prefer-as-const` | `useAsConstAssertion` | ✅ | |
137
+
138
+ #### `eslint-plugin-react` + `react-refresh` (react.mjs)
139
+
140
+ | ESLint 규칙 | Biome 규칙 | 상태 | 비고 |
141
+ |---|---|:---:|---|
142
+ | `react/jsx-key` | `useJsxKeyInIterable` | ✅ | |
143
+ | `react/jsx-no-comment-textnodes` | `noCommentText` | ✅ | |
144
+ | `react/jsx-no-duplicate-props` | `noDuplicateJsxProps` | ✅ | |
145
+ | `react/jsx-no-target-blank` | `noBlankTarget` | ✅ | |
146
+ | `react/jsx-no-useless-fragment` | `noUselessFragments` | ✅ | |
147
+ | `react/no-children-prop` | `noChildrenProp` | ✅ | |
148
+ | `react/no-danger-with-children` | `noDangerouslySetInnerHtmlWithChildren` | ✅ | |
149
+ | `react/void-dom-elements-no-children` | `noVoidElementsWithChildren` | ✅ | |
150
+ | `react-hooks/rules-of-hooks` | `useHookAtTopLevel` | ✅ | |
151
+ | `react-hooks/exhaustive-deps` | `useExhaustiveDependencies` | ✅ | |
152
+ | `react-refresh/only-export-components` | `useComponentExportOnlyModules` | ✅ | |
153
+ | `react/display-name` | — | ❌ | Biome 미구현 |
154
+ | `react/no-unescaped-entities` | — | ❌ | Biome 미구현 |
155
+
156
+ #### Prettier + 스타일 규칙 (prettier.mjs)
157
+
158
+ | ESLint 규칙 | Biome 처리 | 상태 | 비고 |
159
+ |---|---|:---:|---|
160
+ | `prettier/prettier` (포맷 전체) | Biome formatter | ✅ | 단일 도구로 통합 |
161
+ | ↳ `trailingComma: all` | `trailingCommas: "all"` | ✅ | |
162
+ | ↳ `singleQuote: true` | `quoteStyle: "single"` | ✅ | |
163
+ | ↳ `semi: false` | `semicolons: "asNeeded"` | ✅ | |
164
+ | ↳ `tabWidth: 2` | `indentWidth: 2` | ✅ | |
165
+ | ↳ `printWidth: 120` | `lineWidth: 120` | ✅ | |
166
+ | ↳ `arrowParens: always` | `arrowParentheses: "always"` | ✅ | |
167
+ | ↳ `jsxSingleQuote: false` | `jsxQuoteStyle: "double"` | ✅ | |
168
+ | ↳ `@trivago/sort-imports` | `organizeImports` | ✅ | 그룹/정렬/병합 모두 지원 |
169
+ | ↳ `prettier-plugin-tailwindcss` | `useSortedClasses` | ✅ | Biome 내장 |
170
+ | `arrow-body-style` | — | ❌ | Biome 미구현 |
171
+ | `prefer-arrow-callback` | `useArrowFunction` | ✅ | |
172
+ | `curly` | `useBlockStatements` | ✅ | |
173
+ | `lines-around-comment` | — | ❌ | formatter 영역 |
174
+ | `no-confusing-arrow` | — | ❌ | Biome 미구현 |
175
+ | `no-mixed-operators` | — | ❌ | Biome 미구현 |
176
+ | `no-tabs` | formatter `indentStyle: "space"` | ✅ | |
177
+ | `quotes` | formatter `quoteStyle: "single"` | ✅ | |
178
+
179
+ #### `eslint-plugin-import-x` + 커스텀 (hansanghyeon.mjs)
180
+
181
+ | ESLint 규칙 | Biome 처리 | 상태 | 비고 |
182
+ |---|---|:---:|---|
183
+ | `import-x/first` | `organizeImports` chunk | ✅ | |
184
+ | `import-x/newline-after-import` | `organizeImports` `:BLANK_LINE:` | ✅ | |
185
+ | `import-x/no-duplicates` | `organizeImports` 자동 merge | ✅ | |
186
+ | `import-x/no-unresolved` | — | ➖ | 원래 off |
187
+ | `no-empty-pattern` | `noEmptyPattern` | ✅ | off으로 설정 |
188
+ | `camelcase` | `useNamingConvention` | ✅ | off으로 설정 |
189
+ | `class-methods-use-this` | — | ❌ | Biome 미구현 |
190
+ | `no-multi-spaces` | formatter | ✅ | |
191
+
192
+ ### 마이그레이션 커버리지 요약
193
+
194
+ | 카테고리 | 전체 규칙 | ✅ 커버 | ❌ 미커버 | ➖ 해당없음 |
195
+ |---|---|---|---|---|
196
+ | `@eslint/js` recommended | 52 | **50** | 0 | 2 |
197
+ | `typescript-eslint` | 13 | **13** | 0 | 0 |
198
+ | `eslint-plugin-react` + hooks + refresh | 13 | **11** | 2 | 0 |
199
+ | Prettier + 스타일 | 18 | **14** | 4 | 0 |
200
+ | `import-x` + 커스텀 | 8 | **6** | 1 | 1 |
201
+ | **합계** | **104** | **94 (90%)** | **7 (7%)** | **3 (3%)** |
202
+
203
+ 미커버 7개: `react/display-name`, `react/no-unescaped-entities`, `arrow-body-style`, `lines-around-comment`, `no-confusing-arrow`, `no-mixed-operators`, `class-methods-use-this` — 대부분 스타일 취향 규칙이며 실질적 버그 감지에는 영향 없음.
204
+
205
+ ### 요약 매트릭스
206
+
207
+ | | 속도 | 룰 커버리지 | import DX | 단일 도구 | auto-fix |
208
+ |---|:---:|:---:|:---:|:---:|:---:|
209
+ | **oxlint+oxfmt** | ⭐⭐⭐ | ⭐⭐ | ⭐ | ❌ 2개 | ⚠️ |
210
+ | **Biome** | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ✅ 1개 | ✅ |
211
+ | **ESLint+Prettier** | ⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ❌ 2개+ | ✅ |
212
+
213
+ ## 프로젝트별 셋업 가이드
214
+
215
+ > Biome은 파일 확장자로 언어를 자동 감지합니다. `.ts`, `.tsx` 파일은 TS/React 규칙이 자동 적용됩니다.
216
+ > `@hyeon/linter/biome-config`를 `extends`로 상속하면 포맷+린트+import 정리 설정이 한 번에 적용됩니다.
217
+
218
+ ### 공통 설치
219
+
220
+ ```bash
221
+ npm install --save-dev @hyeon/linter @biomejs/biome
222
+ ```
223
+
224
+ ### 공통 scripts
225
+
226
+ ```json
227
+ {
228
+ "scripts": {
229
+ "lint": "biome ci .",
230
+ "lint:fix": "biome check --write ."
231
+ }
232
+ }
233
+ ```
234
+
235
+ ---
236
+
237
+ ### 1) JavaScript 프로젝트
238
+
239
+ ```jsonc
240
+ // biome.jsonc
241
+ {
242
+ "extends": ["@hyeon/linter/biome-config"]
243
+ }
17
244
  ```
18
245
 
19
- ## vite
246
+ 이것만으로 JS recommended 린트 + 포맷 + import 정리가 활성화됩니다.
20
247
 
21
- uninstall
248
+ ---
249
+
250
+ ### 2) TypeScript 프로젝트
251
+
252
+ ```jsonc
253
+ // biome.jsonc
254
+ {
255
+ "extends": ["@hyeon/linter/biome-config"]
256
+ }
257
+ ```
258
+
259
+ `.ts` 파일에 대해 TypeScript 규칙(`useImportType`, `noExplicitAny` 등)이 자동 적용됩니다.
260
+ 추가 커스터마이징:
261
+
262
+ ```jsonc
263
+ {
264
+ "extends": ["@hyeon/linter/biome-config"],
265
+ "linter": {
266
+ "rules": {
267
+ "suspicious": {
268
+ // any를 엄격히 금지하고 싶은 경우
269
+ "noExplicitAny": "error"
270
+ }
271
+ }
272
+ }
273
+ }
274
+ ```
275
+
276
+ ---
277
+
278
+ ### 3) React 프로젝트
279
+
280
+ ```jsonc
281
+ // biome.jsonc
282
+ {
283
+ "extends": ["@hyeon/linter/biome-config"]
284
+ }
285
+ ```
286
+
287
+ `.tsx` 파일에 대해 React 규칙(`useJsxKeyInIterable`, `useHookAtTopLevel`, `useExhaustiveDependencies` 등)이 자동 적용됩니다.
288
+ 별도 React 프리셋 설치가 필요 없습니다.
289
+
290
+ ---
291
+
292
+ ### 4) TypeScript + React (풀셋)
293
+
294
+ ```jsonc
295
+ // biome.jsonc
296
+ {
297
+ "extends": ["@hyeon/linter/biome-config"]
298
+ }
299
+ ```
300
+
301
+ TS + React 규칙이 모두 자동 적용됩니다. ESLint에서는 5개 프리셋을 조합해야 했지만 Biome은 1줄입니다.
302
+
303
+ ---
304
+
305
+ ### 5) Next.js 프로젝트
306
+
307
+ ```jsonc
308
+ // biome.jsonc
309
+ {
310
+ "extends": ["@hyeon/linter/biome-config"],
311
+ "linter": {
312
+ "rules": {
313
+ "nursery": {
314
+ // Next.js 전용 규칙 활성화
315
+ "noImgElement": "error",
316
+ "noHeadElement": "error"
317
+ }
318
+ }
319
+ },
320
+ "overrides": [
321
+ {
322
+ "includes": [".next/**"],
323
+ "linter": { "enabled": false },
324
+ "formatter": { "enabled": false }
325
+ }
326
+ ]
327
+ }
328
+ ```
329
+
330
+ ---
331
+
332
+ ### 6) NestJS / 백엔드 프로젝트
333
+
334
+ ```jsonc
335
+ // biome.jsonc
336
+ {
337
+ "extends": ["@hyeon/linter/biome-config"],
338
+ "linter": {
339
+ "rules": {
340
+ "style": {
341
+ // NestJS 데코레이터 패턴에서 빈 클래스 허용
342
+ "noUselessConstructor": "off"
343
+ }
344
+ }
345
+ },
346
+ "overrides": [
347
+ {
348
+ "includes": ["dist/**"],
349
+ "linter": { "enabled": false },
350
+ "formatter": { "enabled": false }
351
+ }
352
+ ]
353
+ }
354
+ ```
355
+
356
+ ---
357
+
358
+ ### 7) 모노레포
359
+
360
+ 루트에 `biome.jsonc`를 두고, 각 패키지에서 상속합니다:
361
+
362
+ ```
363
+ monorepo/
364
+ ├── biome.jsonc # extends @hyeon/linter/biome-config
365
+ ├── packages/
366
+ │ ├── web/
367
+ │ │ └── biome.jsonc # extends ../../biome.jsonc + Next.js 오버라이드
368
+ │ └── api/
369
+ │ └── biome.jsonc # extends ../../biome.jsonc + NestJS 오버라이드
370
+ ```
371
+
372
+ ```jsonc
373
+ // packages/web/biome.jsonc
374
+ {
375
+ "extends": ["../../biome.jsonc"],
376
+ "linter": {
377
+ "rules": {
378
+ "nursery": {
379
+ "noImgElement": "error"
380
+ }
381
+ }
382
+ }
383
+ }
384
+ ```
385
+
386
+ ---
387
+
388
+ ### ESLint 프리셋 대응표
389
+
390
+ | 기존 ESLint 프리셋 | Biome 설정 |
391
+ |---|---|
392
+ | `@hyeon/linter/recommended` | `"extends": ["@hyeon/linter/biome-config"]` (기본 포함) |
393
+ | `@hyeon/linter/typescript` | 자동 적용 (`.ts` 파일 감지) |
394
+ | `@hyeon/linter/react` | 자동 적용 (`.tsx` 파일 감지) |
395
+ | `@hyeon/linter/prettier` | 기본 formatter로 통합 |
396
+ | `@hyeon/linter/hansanghyeon` | 기본 설정에 통합 (`noExplicitAny: off` 등) |
397
+
398
+ **ESLint에서 5개 프리셋을 조합하던 것이 Biome에서는 `extends` 1줄로 끝납니다.**
399
+
400
+ ### 실행
22
401
 
23
402
  ```bash
24
- eslint-plugin-react-hooks eslint-plugin-react-refresh typescript-eslint globals @eslint/js
403
+ # lint + format + import 정리 한 번에
404
+ npx @biomejs/biome check --write .
405
+
406
+ # CI (검사만, 수정 없음)
407
+ npx @biomejs/biome ci .
25
408
  ```
409
+
410
+ ## v10 → v11 마이그레이션
411
+
412
+ v11은 **Breaking Change**입니다. ESLint/Prettier를 Biome으로 완전 교체합니다.
413
+
414
+ ### 마이그레이션 방법
415
+
416
+ 1. `eslint.config.mjs` 삭제
417
+ 2. `biome.jsonc` 생성 또는 `@hyeon/linter/biome-config` 상속
418
+ 3. `eslint`, `prettier` 및 관련 플러그인 devDependencies 제거
419
+ 4. `@biomejs/biome` 설치
420
+ 5. lint 스크립트를 `biome check` 또는 `biome ci` 로 변경
package/biome.jsonc ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "linter": {
9
+ "enabled": true,
10
+ "rules": {
11
+ "recommended": true,
12
+ "suspicious": {
13
+ "noExplicitAny": "off"
14
+ },
15
+ "style": {
16
+ "useImportType": {
17
+ "level": "error",
18
+ "options": { "style": "inlineType" }
19
+ },
20
+ "noUnusedTemplateLiteral": "off"
21
+ },
22
+ "correctness": {
23
+ "noUnusedVariables": "error",
24
+ "noUnusedImports": "error"
25
+ },
26
+ "nursery": {}
27
+ }
28
+ },
29
+ "formatter": {
30
+ "enabled": true,
31
+ "indentStyle": "space",
32
+ "indentWidth": 2,
33
+ "lineWidth": 120,
34
+ "lineEnding": "lf"
35
+ },
36
+ "javascript": {
37
+ "formatter": {
38
+ "quoteStyle": "single",
39
+ "jsxQuoteStyle": "double",
40
+ "trailingCommas": "all",
41
+ "semicolons": "asNeeded",
42
+ "arrowParentheses": "always"
43
+ }
44
+ },
45
+ "assist": {
46
+ "actions": {
47
+ "source": {
48
+ "organizeImports": {
49
+ "level": "on",
50
+ "options": {
51
+ "groups": [
52
+ ":URL:",
53
+ [":BUN:", ":NODE:"],
54
+ ":BLANK_LINE:",
55
+ ":PACKAGE:",
56
+ ":BLANK_LINE:",
57
+ ":ALIAS:",
58
+ ":BLANK_LINE:",
59
+ ":PATH:"
60
+ ]
61
+ }
62
+ }
63
+ }
64
+ }
65
+ },
66
+ "overrides": [
67
+ {
68
+ "includes": ["*.json", "*.jsonc"],
69
+ "formatter": {
70
+ "indentWidth": 2
71
+ }
72
+ },
73
+ {
74
+ "includes": ["test/src/fixtures/**", "test/src/examples/**"],
75
+ "linter": { "enabled": false },
76
+ "assist": { "enabled": false }
77
+ }
78
+ ]
79
+ }
package/package.json CHANGED
@@ -1,70 +1,54 @@
1
1
  {
2
2
  "name": "@hyeon/linter",
3
- "version": "10.0.1-dev.1.29",
4
- "description": "Hansanghyun custom eslint settings",
3
+ "version": "11.0.0",
4
+ "description": "Biome-based lint, format, and import organize configuration",
5
5
  "main": "./src/index.mjs",
6
+ "type": "module",
6
7
  "scripts": {
7
- "lint": "eslint src/",
8
- "test": "npm run lint"
8
+ "lint": "biome ci .",
9
+ "lint:fix": "biome check --write .",
10
+ "test": "npm run lint && npm --prefix test run test:all"
9
11
  },
10
12
  "repository": {
11
13
  "type": "git",
12
- "url": "git+https://git.hyeon.pro/hansanghyeon/linter.git"
14
+ "url": "git+https://git.hyeon.pro/space/tools/linter.git"
13
15
  },
14
16
  "keywords": [
15
- "eslint",
16
- "eslintconfig",
17
+ "biome",
18
+ "linter",
19
+ "formatter",
17
20
  "hyeon",
18
21
  "javascript",
19
22
  "react",
20
23
  "nextjs",
21
24
  "nestjs",
22
- "typescript",
23
- "prettier"
25
+ "typescript"
24
26
  ],
25
27
  "author": "hyeon",
26
28
  "license": "MIT",
27
29
  "bugs": {
28
- "url": "https://git.hyeon.pro/hansanghyeon/linter/issues"
30
+ "url": "https://git.hyeon.pro/space/tools/linter/issues"
29
31
  },
30
- "homepage": "https://git.hyeon.pro/hansanghyeon/linter#readme",
32
+ "homepage": "https://git.hyeon.pro/space/tools/linter#readme",
31
33
  "dependencies": {
32
- "@eslint/js": "^10.0.1",
33
- "@trivago/prettier-plugin-sort-imports": "^6.0.2",
34
- "eslint-config-prettier": "^10.1.8",
35
- "eslint-plugin-import-x": "^4.16.1",
36
- "eslint-plugin-prettier": "^5.5.5",
37
- "eslint-plugin-react": "^7.37.5",
38
- "eslint-plugin-react-refresh": "^0.5.0",
39
- "prettier": "^3.5.3",
40
- "prettier-plugin-tailwindcss": "^0.7.2",
41
- "typescript": "^5.9.3",
42
- "typescript-eslint": "^8.55.0"
34
+ "typescript": "^5.9.3"
43
35
  },
44
36
  "peerDependencies": {
45
- "eslint": "^10.0.0",
46
- "prettier": "^3.5.3"
37
+ "@biomejs/biome": ">=2.0.0"
47
38
  },
48
39
  "devDependencies": {
49
- "eslint": "^10.0.0"
40
+ "@biomejs/biome": "^2.4.11"
50
41
  },
51
42
  "exports": {
52
43
  ".": {
53
44
  "import": "./src/index.mjs",
54
- "require": "./src/index.mjs",
55
45
  "default": "./src/index.mjs"
56
46
  },
57
- "./recommended": "./src/base.mjs",
58
- "./typescript": "./src/typescript.mjs",
59
- "./react": "./src/react.mjs",
60
- "./prettier": "./src/prettier.mjs",
61
- "./hansanghyeon": "./src/hansanghyeon.mjs",
47
+ "./biome-config": "./biome.jsonc",
62
48
  "./package.json": "./package.json"
63
49
  },
64
50
  "files": [
65
- "typescript",
66
- "react",
67
- "prettier",
68
- "src"
51
+ "src",
52
+ "biome.jsonc"
69
53
  ]
70
54
  }
package/src/index.mjs CHANGED
@@ -1,29 +1,23 @@
1
- import hyeonEslintConfigBase from './base.mjs'
2
- import hyeonEslintConfigHansanghyeon from './hansanghyeon.mjs'
3
- import hyeonEslintConfigPrettier from './prettier.mjs'
4
- import hyeonEslintConfigReact from './react.mjs'
5
- import hyeonEslintConfigTypescript from './typescript.mjs'
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
6
4
 
7
- // 기존 사용자를 위한 객체 형태 유지 (각 속성이 flat config 배열)
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
8
6
 
9
- const configs = {
10
- recommended: hyeonEslintConfigBase,
11
- prettier: hyeonEslintConfigPrettier,
12
- react: hyeonEslintConfigReact,
13
- typescript: hyeonEslintConfigTypescript,
14
- hansanghyeon: hyeonEslintConfigHansanghyeon,
15
- }
16
-
17
- // Default export는 설정 객체
18
-
19
- export default configs
7
+ /**
8
+ * @hyeon/linter Biome 설정을 반환합니다.
9
+ * biome.jsonc를 파싱하여 JSON 객체로 제공합니다.
10
+ */
11
+ export function getConfig() {
12
+ const configPath = join(__dirname, '..', 'biome.jsonc')
13
+ const raw = readFileSync(configPath, 'utf8')
20
14
 
21
- // Named exports로도 개별 설정 제공
22
-
23
- export {
24
- hyeonEslintConfigBase as recommended,
25
- hyeonEslintConfigPrettier as prettier,
26
- hyeonEslintConfigReact as react,
27
- hyeonEslintConfigTypescript as typescript,
28
- hyeonEslintConfigHansanghyeon as hansanghyeon,
15
+ // JSONC 파싱 (주석/trailing comma 제거)
16
+ const stripped = raw
17
+ .replace(/\/\*[\s\S]*?\*\//g, '')
18
+ .replace(/(^|[^:])\/\/.*$/gm, '$1')
19
+ .replace(/,\s*([\]}])/g, '$1')
20
+ return JSON.parse(stripped)
29
21
  }
22
+
23
+ export default getConfig
package/src/base.mjs DELETED
@@ -1,3 +0,0 @@
1
- import js from '@eslint/js'
2
-
3
- export default [js.configs.recommended]
@@ -1,50 +0,0 @@
1
- import importX from 'eslint-plugin-import-x'
2
-
3
- const _import = [
4
- importX.flatConfigs.recommended,
5
- {
6
- rules: {
7
- 'import-x/first': 'error',
8
- 'import-x/newline-after-import': 'error',
9
- 'import-x/no-duplicates': 'error',
10
- 'import-x/no-unresolved': 'off',
11
- },
12
- },
13
- ]
14
-
15
- const custom = {
16
- rules: {
17
- 'no-empty-pattern': 'off',
18
- 'lines-around-comment': 'off',
19
- camelcase: 'off',
20
- 'class-methods-use-this': 'off',
21
- 'no-multi-spaces': [
22
- 'error',
23
- {
24
- ignoreEOLComments: true,
25
- },
26
- ],
27
-
28
- // 보일러플레이트 코드를 생성할때 {} 를 사용하는 경우가 많아서 off
29
-
30
- '@typescript-eslint/no-empty-object-type': 'off',
31
- '@typescript-eslint/no-explicit-any': 'off',
32
- '@typescript-eslint/no-unused-vars': [
33
- 'error',
34
- {
35
- vars: 'all',
36
- varsIgnorePattern: '^_.*$|^_$|^P$|^flow$|^pipe$',
37
- args: 'all',
38
- argsIgnorePattern: '^_.*$|^_$|^P$|^flow$|^pipe$',
39
- caughtErrors: 'all',
40
- caughtErrorsIgnorePattern: '^_.*$|^_$|^P$|^flow$|^pipe$',
41
- destructuredArrayIgnorePattern: '^_.*$|^_$|^P$|^flow$|^pipe$',
42
- ignoreRestSiblings: true,
43
- },
44
- ],
45
- 'no-confusing-arrow': 'off',
46
- },
47
- ignores: ['.config/*', 'eslint.config.mjs', 'eslint.config.js'],
48
- }
49
-
50
- export default [custom, ..._import]
package/src/prettier.mjs DELETED
@@ -1,68 +0,0 @@
1
- import eslintConfigPrettier from 'eslint-config-prettier'
2
- import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
3
-
4
- export default [
5
- eslintConfigPrettier,
6
- {
7
- ...eslintPluginPrettierRecommended,
8
- rules: {
9
- 'prettier/prettier': [
10
- 'error',
11
- {
12
- trailingComma: 'all',
13
- singleQuote: true,
14
- semi: false,
15
- tabWidth: 2,
16
- printWidth: 120,
17
- arrowParens: 'always',
18
- jsxSingleQuote: false,
19
- plugins: ['@trivago/prettier-plugin-sort-imports', 'prettier-plugin-tailwindcss'],
20
- importOrder: ['^[^.](.*)$', '^[.]{1,2}/.*$'],
21
- importOrderSeparation: false,
22
- importOrderSortSpecifiers: true,
23
-
24
- /**
25
- * @see https://github.com/trivago/prettier-plugin-sort-imports/issues/120
26
- */
27
-
28
- importOrderParserPlugins: ['typescript', 'decorators-legacy', 'jsx', 'classProperties'],
29
- },
30
- ],
31
- 'arrow-body-style': ['error', 'as-needed'],
32
- 'prefer-arrow-callback': 'error',
33
- curly: ['error', 'all'],
34
- 'lines-around-comment': [
35
- 'error',
36
- {
37
- beforeBlockComment: true,
38
- afterBlockComment: true,
39
- beforeLineComment: true,
40
- afterLineComment: true,
41
- allowBlockStart: true,
42
- allowBlockEnd: true,
43
- allowObjectStart: true,
44
- allowObjectEnd: true,
45
- allowArrayStart: true,
46
- allowArrayEnd: true,
47
- },
48
- ],
49
- 'max-len': 'off',
50
- 'no-confusing-arrow': [
51
- 'error',
52
- {
53
- allowParens: false,
54
- },
55
- ],
56
- 'no-mixed-operators': 'error',
57
- 'no-tabs': 'error',
58
- quotes: [
59
- 'error',
60
- 'single',
61
- {
62
- avoidEscape: true,
63
- allowTemplateLiterals: false,
64
- },
65
- ],
66
- },
67
- },
68
- ]
package/src/react.mjs DELETED
@@ -1,17 +0,0 @@
1
- import react from 'eslint-plugin-react'
2
- import { reactRefresh } from 'eslint-plugin-react-refresh'
3
-
4
- export default [
5
- {
6
- settings: { react: { version: '19.0' } },
7
- plugins: {
8
- react,
9
- 'react-refresh': reactRefresh.plugin,
10
- },
11
- rules: {
12
- ...react.configs.recommended.rules,
13
- ...react.configs['jsx-runtime'].rules,
14
- ...reactRefresh.configs.recommended().rules,
15
- },
16
- },
17
- ]
@@ -1,50 +0,0 @@
1
- import js from '@eslint/js'
2
- import eslintConfigPrettier from 'eslint-config-prettier'
3
- import tseslint from 'typescript-eslint'
4
-
5
- // tseslint.config()는 이미 flat config 배열을 반환
6
-
7
- const config = [
8
- {
9
- ignores: ['**/node_modules/**', '**/dist/**', '**/.turbo/**', '**/coverage/**'],
10
- },
11
- js.configs.recommended,
12
- ...tseslint.configs.recommended,
13
- eslintConfigPrettier,
14
- {
15
- files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
16
- languageOptions: {
17
- parser: tseslint.parser,
18
- parserOptions: {
19
- ecmaVersion: 'latest',
20
- sourceType: 'module',
21
- ecmaFeatures: {
22
- decorators: true,
23
- },
24
- },
25
- },
26
- rules: {
27
- '@typescript-eslint/no-explicit-any': 'off',
28
- '@typescript-eslint/no-unused-vars': [
29
- 'error',
30
- {
31
- argsIgnorePattern: '^_',
32
- varsIgnorePattern: '^_',
33
- },
34
- ],
35
- '@typescript-eslint/consistent-type-imports': [
36
- 'error',
37
- {
38
- prefer: 'type-imports',
39
- fixStyle: 'inline-type-imports',
40
- disallowTypeAnnotations: false,
41
- },
42
- ],
43
- '@typescript-eslint/explicit-function-return-type': 'off',
44
- '@typescript-eslint/explicit-module-boundary-types': 'off',
45
- '@typescript-eslint/no-non-null-assertion': 'off',
46
- },
47
- },
48
- ]
49
-
50
- export default config