@hyeon/linter 11.0.1-dev.3.50 → 11.0.2
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 +105 -16
- package/biome.jsonc +11 -3
- package/package.json +3 -2
- package/src/index.mjs +134 -6
package/README.md
CHANGED
|
@@ -1,16 +1,92 @@
|
|
|
1
1
|
# @hyeon/linter
|
|
2
2
|
|
|
3
|
+
패키지 매니저에 맞춰 설치하세요.
|
|
4
|
+
|
|
3
5
|
```bash
|
|
4
6
|
npm install --save-dev @hyeon/linter @biomejs/biome
|
|
7
|
+
pnpm add -D @hyeon/linter @biomejs/biome
|
|
8
|
+
yarn add -D @hyeon/linter @biomejs/biome
|
|
9
|
+
bun add -d @hyeon/linter @biomejs/biome
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## AI 셋업 프롬프트
|
|
14
|
+
|
|
15
|
+
아래 코드블럭을 복사해서 AI에게 붙여넣으세요.
|
|
16
|
+
|
|
17
|
+
````
|
|
18
|
+
@hyeon/linter를 이 프로젝트에 설치해줘.
|
|
19
|
+
|
|
20
|
+
설치:
|
|
21
|
+
프로젝트의 `packageManager`를 확인하고 맞는 명령을 사용해 설치해줘. `packageManager`가 없으면 npm을 사용해줘.
|
|
22
|
+
```bash
|
|
23
|
+
npm install --save-dev @hyeon/linter @biomejs/biome
|
|
24
|
+
pnpm add -D @hyeon/linter @biomejs/biome
|
|
25
|
+
yarn add -D @hyeon/linter @biomejs/biome
|
|
26
|
+
bun add -d @hyeon/linter @biomejs/biome
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
프로젝트 루트에 biome.jsonc 파일을 생성하고 아래 내용을 넣어줘:
|
|
30
|
+
```jsonc
|
|
31
|
+
{
|
|
32
|
+
"extends": ["@hyeon/linter/biome-config"]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
package.json scripts에 아래를 추가해줘:
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"lint": "biome ci .",
|
|
40
|
+
"lint:fix": "biome check --write .",
|
|
41
|
+
"lint:fix:unsafe": "biome check --write --unsafe ."
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
.vscode/settings.json에 아래를 추가해줘:
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
49
|
+
"editor.formatOnSave": true,
|
|
50
|
+
"editor.codeActionsOnSave": {
|
|
51
|
+
"source.fixAll.biome": "explicit",
|
|
52
|
+
"source.organizeImports.biome": "explicit"
|
|
53
|
+
},
|
|
54
|
+
"biome.enabled": true,
|
|
55
|
+
"[javascript]": {
|
|
56
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
57
|
+
},
|
|
58
|
+
"[javascriptreact]": {
|
|
59
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
60
|
+
},
|
|
61
|
+
"[typescript]": {
|
|
62
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
63
|
+
},
|
|
64
|
+
"[typescriptreact]": {
|
|
65
|
+
"editor.defaultFormatter": "biomejs.biome"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
5
68
|
```
|
|
6
69
|
|
|
7
|
-
|
|
70
|
+
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만 저장 시 적용하는 구성이 더 안정적이야.
|
|
71
|
+
|
|
72
|
+
.vscode/extensions.json에 아래를 추가해줘:
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"recommendations": ["biomejs.biome"]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
설치 후 biome check --write . 로 전체 포맷을 적용해줘.
|
|
80
|
+
`useOptionalChain`처럼 Biome이 unsafe fix로 분류한 수정까지 적용해야 하면 biome check --write --unsafe . 를 별도로 실행해줘.
|
|
81
|
+
````
|
|
82
|
+
|
|
8
83
|
|
|
9
84
|
## 지원 정책
|
|
10
85
|
|
|
11
|
-
- TypeScript 5.9+
|
|
86
|
+
- TypeScript 프로젝트는 TypeScript 5.9+ 기준
|
|
12
87
|
- Biome 기반 (lint + format + import organize 단일 도구)
|
|
13
88
|
- ESLint/Prettier 레거시 경로는 v11에서 완전 제거됨
|
|
89
|
+
- Tailwind CSS v4의 `@theme`, `@utility` 같은 CSS 지시문은 기본 설정에서 파싱 가능
|
|
14
90
|
|
|
15
91
|
## 왜 Biome인가
|
|
16
92
|
|
|
@@ -30,10 +106,10 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
30
106
|
|
|
31
107
|
### 룰 커버리지
|
|
32
108
|
|
|
33
|
-
| 도구 | 총 룰 수 |
|
|
109
|
+
| 도구 | 총 룰 수 | 타입 기반 분석 | auto-fix | 플러그인 생태계 |
|
|
34
110
|
|---|---|---|---|---|
|
|
35
111
|
| **ESLint** | 700+ (+4000 플러그인) | ✅ 완전 | ✅ 우수 | 4000+ |
|
|
36
|
-
| **Biome v2** | 423+ |
|
|
112
|
+
| **Biome v2** | 423+ | ⚠️ 일부 프로젝트 기반 분석 | ✅ 양호 | 성장 중 |
|
|
37
113
|
| **oxlint** | ~300 | ✅ tsgolint 43룰 | ⚠️ 제한적 | 최소 |
|
|
38
114
|
|
|
39
115
|
### import DX 커버리지 — Biome 선택 이유
|
|
@@ -124,8 +200,8 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
124
200
|
| `@ts/no-explicit-any` | `noExplicitAny` | ✅ | off으로 설정 |
|
|
125
201
|
| `@ts/no-unused-vars` | `noUnusedVariables` + `noUnusedFunctionParameters` | ✅ | `_` 접두사 패턴 지원 |
|
|
126
202
|
| `@ts/consistent-type-imports` | `useImportType` | ✅ | `inlineType` 스타일 지원 |
|
|
127
|
-
| `@ts/explicit-function-return-type` | `useExplicitType` | ✅ |
|
|
128
|
-
| `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ |
|
|
203
|
+
| `@ts/explicit-function-return-type` | `useExplicitType` | ✅ | nursery 규칙이라 기본 미활성화 |
|
|
204
|
+
| `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ | nursery 규칙이라 기본 미활성화 |
|
|
129
205
|
| `@ts/no-non-null-assertion` | `noNonNullAssertion` | ✅ | off으로 설정 |
|
|
130
206
|
| `@ts/no-empty-interface` | `noEmptyInterface` | ✅ | |
|
|
131
207
|
| `@ts/no-empty-object-type` | `noBannedTypes` | ✅ | |
|
|
@@ -150,6 +226,7 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
150
226
|
| `react-hooks/rules-of-hooks` | `useHookAtTopLevel` | ✅ | |
|
|
151
227
|
| `react-hooks/exhaustive-deps` | `useExhaustiveDependencies` | ✅ | |
|
|
152
228
|
| `react-refresh/only-export-components` | `useComponentExportOnlyModules` | ✅ | |
|
|
229
|
+
| `react/jsx-curly-brace-presence` | `useConsistentCurlyBraces` | ✅ | 문자열 JSX props/children은 중괄호 없이 작성 |
|
|
153
230
|
| `react/display-name` | — | ❌ | Biome 미구현 |
|
|
154
231
|
| `react/no-unescaped-entities` | — | ❌ | Biome 미구현 |
|
|
155
232
|
|
|
@@ -166,7 +243,7 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
166
243
|
| ↳ `arrowParens: always` | `arrowParentheses: "always"` | ✅ | |
|
|
167
244
|
| ↳ `jsxSingleQuote: false` | `jsxQuoteStyle: "double"` | ✅ | |
|
|
168
245
|
| ↳ `@trivago/sort-imports` | `organizeImports` | ✅ | 그룹/정렬/병합 모두 지원 |
|
|
169
|
-
| ↳ `prettier-plugin-tailwindcss` | `useSortedClasses` |
|
|
246
|
+
| ↳ `prettier-plugin-tailwindcss` | `useSortedClasses` | ⚠️ | Biome 내장 규칙이지만 unsafe fix라 기본 미활성화 |
|
|
170
247
|
| `arrow-body-style` | — | ❌ | Biome 미구현 |
|
|
171
248
|
| `prefer-arrow-callback` | `useArrowFunction` | ✅ | |
|
|
172
249
|
| `curly` | `useBlockStatements` | ✅ | |
|
|
@@ -185,7 +262,7 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
185
262
|
| `import-x/no-duplicates` | `organizeImports` 자동 merge | ✅ | |
|
|
186
263
|
| `import-x/no-unresolved` | — | ➖ | 원래 off |
|
|
187
264
|
| `no-empty-pattern` | `noEmptyPattern` | ✅ | off으로 설정 |
|
|
188
|
-
| `camelcase` | `useNamingConvention` | ✅ |
|
|
265
|
+
| `camelcase` | `useNamingConvention` | ✅ | 기본 미활성화 |
|
|
189
266
|
| `class-methods-use-this` | — | ❌ | Biome 미구현 |
|
|
190
267
|
| `no-multi-spaces` | formatter | ✅ | |
|
|
191
268
|
|
|
@@ -219,6 +296,9 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
219
296
|
|
|
220
297
|
```bash
|
|
221
298
|
npm install --save-dev @hyeon/linter @biomejs/biome
|
|
299
|
+
pnpm add -D @hyeon/linter @biomejs/biome
|
|
300
|
+
yarn add -D @hyeon/linter @biomejs/biome
|
|
301
|
+
bun add -d @hyeon/linter @biomejs/biome
|
|
222
302
|
```
|
|
223
303
|
|
|
224
304
|
### 공통 scripts
|
|
@@ -227,11 +307,14 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
227
307
|
{
|
|
228
308
|
"scripts": {
|
|
229
309
|
"lint": "biome ci .",
|
|
230
|
-
"lint:fix": "biome check --write ."
|
|
310
|
+
"lint:fix": "biome check --write .",
|
|
311
|
+
"lint:fix:unsafe": "biome check --write --unsafe ."
|
|
231
312
|
}
|
|
232
313
|
}
|
|
233
314
|
```
|
|
234
315
|
|
|
316
|
+
`lint:fix`는 safe fix와 formatter를 적용합니다. `useOptionalChain` 같은 unsafe fix까지 적용해야 할 때만 `lint:fix:unsafe`를 수동으로 실행하세요.
|
|
317
|
+
|
|
235
318
|
---
|
|
236
319
|
|
|
237
320
|
### 1) JavaScript 프로젝트
|
|
@@ -310,9 +393,12 @@ TS + React 규칙이 모두 자동 적용됩니다. ESLint에서는 5개 프리
|
|
|
310
393
|
"extends": ["@hyeon/linter/biome-config"],
|
|
311
394
|
"linter": {
|
|
312
395
|
"rules": {
|
|
313
|
-
"
|
|
314
|
-
// Next.js 전용 규칙 활성화
|
|
315
|
-
"noImgElement": "error"
|
|
396
|
+
"performance": {
|
|
397
|
+
// Next.js 전용 이미지 최적화 규칙 활성화
|
|
398
|
+
"noImgElement": "error"
|
|
399
|
+
},
|
|
400
|
+
"style": {
|
|
401
|
+
// Next.js 전용 head 요소 규칙 활성화
|
|
316
402
|
"noHeadElement": "error"
|
|
317
403
|
}
|
|
318
404
|
}
|
|
@@ -337,7 +423,7 @@ TS + React 규칙이 모두 자동 적용됩니다. ESLint에서는 5개 프리
|
|
|
337
423
|
"extends": ["@hyeon/linter/biome-config"],
|
|
338
424
|
"linter": {
|
|
339
425
|
"rules": {
|
|
340
|
-
"
|
|
426
|
+
"complexity": {
|
|
341
427
|
// NestJS 데코레이터 패턴에서 빈 클래스 허용
|
|
342
428
|
"noUselessConstructor": "off"
|
|
343
429
|
}
|
|
@@ -375,7 +461,7 @@ monorepo/
|
|
|
375
461
|
"extends": ["../../biome.jsonc"],
|
|
376
462
|
"linter": {
|
|
377
463
|
"rules": {
|
|
378
|
-
"
|
|
464
|
+
"performance": {
|
|
379
465
|
"noImgElement": "error"
|
|
380
466
|
}
|
|
381
467
|
}
|
|
@@ -401,10 +487,13 @@ monorepo/
|
|
|
401
487
|
|
|
402
488
|
```bash
|
|
403
489
|
# lint + format + import 정리 한 번에
|
|
404
|
-
npx
|
|
490
|
+
npx biome check --write .
|
|
491
|
+
|
|
492
|
+
# unsafe fix까지 적용해야 할 때만 수동 실행
|
|
493
|
+
npx biome check --write --unsafe .
|
|
405
494
|
|
|
406
495
|
# CI (검사만, 수정 없음)
|
|
407
|
-
npx
|
|
496
|
+
npx biome ci .
|
|
408
497
|
```
|
|
409
498
|
|
|
410
499
|
## v10 → v11 마이그레이션
|
package/biome.jsonc
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.4.
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.4.13/schema.json",
|
|
3
3
|
"vcs": {
|
|
4
4
|
"enabled": true,
|
|
5
5
|
"clientKind": "git",
|
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
"level": "error",
|
|
18
18
|
"options": { "style": "inlineType" }
|
|
19
19
|
},
|
|
20
|
-
"noUnusedTemplateLiteral": "off"
|
|
20
|
+
"noUnusedTemplateLiteral": "off",
|
|
21
|
+
"noNonNullAssertion": "off",
|
|
22
|
+
"useConsistentCurlyBraces": "error"
|
|
21
23
|
},
|
|
22
24
|
"correctness": {
|
|
23
25
|
"noUnusedVariables": "error",
|
|
24
|
-
"noUnusedImports": "error"
|
|
26
|
+
"noUnusedImports": "error",
|
|
27
|
+
"noEmptyPattern": "off"
|
|
25
28
|
},
|
|
26
29
|
"nursery": {}
|
|
27
30
|
}
|
|
@@ -42,6 +45,11 @@
|
|
|
42
45
|
"arrowParentheses": "always"
|
|
43
46
|
}
|
|
44
47
|
},
|
|
48
|
+
"css": {
|
|
49
|
+
"parser": {
|
|
50
|
+
"tailwindDirectives": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
45
53
|
"assist": {
|
|
46
54
|
"actions": {
|
|
47
55
|
"source": {
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyeon/linter",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.2",
|
|
4
4
|
"description": "Biome-based lint, format, and import organize configuration",
|
|
5
5
|
"main": "./src/index.mjs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"lint": "biome ci .",
|
|
9
9
|
"lint:fix": "biome check --write .",
|
|
10
|
+
"lint:fix:unsafe": "biome check --write --unsafe .",
|
|
10
11
|
"test": "npm run lint && npm --prefix test run test:all"
|
|
11
12
|
},
|
|
12
13
|
"repository": {
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"@biomejs/biome": ">=2.0.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@biomejs/biome": "^2.4.
|
|
41
|
+
"@biomejs/biome": "^2.4.13"
|
|
41
42
|
},
|
|
42
43
|
"exports": {
|
|
43
44
|
".": {
|
package/src/index.mjs
CHANGED
|
@@ -12,12 +12,140 @@ export function getConfig() {
|
|
|
12
12
|
const configPath = join(__dirname, '..', 'biome.jsonc')
|
|
13
13
|
const raw = readFileSync(configPath, 'utf8')
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
return JSON.parse(stripJsonc(raw))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function stripJsonc(raw) {
|
|
19
|
+
return stripTrailingCommas(stripComments(raw))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function stripComments(raw) {
|
|
23
|
+
let result = ''
|
|
24
|
+
let inString = false
|
|
25
|
+
let stringQuote = ''
|
|
26
|
+
let escaped = false
|
|
27
|
+
let inLineComment = false
|
|
28
|
+
let inBlockComment = false
|
|
29
|
+
|
|
30
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
31
|
+
const char = raw[index]
|
|
32
|
+
const nextChar = raw[index + 1]
|
|
33
|
+
|
|
34
|
+
if (inLineComment) {
|
|
35
|
+
if (char === '\n' || char === '\r') {
|
|
36
|
+
inLineComment = false
|
|
37
|
+
result += char
|
|
38
|
+
}
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (inBlockComment) {
|
|
43
|
+
if (char === '*' && nextChar === '/') {
|
|
44
|
+
inBlockComment = false
|
|
45
|
+
index += 1
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (char === '\n' || char === '\r') {
|
|
49
|
+
result += char
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (inString) {
|
|
55
|
+
result += char
|
|
56
|
+
|
|
57
|
+
if (escaped) {
|
|
58
|
+
escaped = false
|
|
59
|
+
} else if (char === '\\') {
|
|
60
|
+
escaped = true
|
|
61
|
+
} else if (char === stringQuote) {
|
|
62
|
+
inString = false
|
|
63
|
+
stringQuote = ''
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (char === '"' || char === "'") {
|
|
70
|
+
inString = true
|
|
71
|
+
stringQuote = char
|
|
72
|
+
result += char
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (char === '/' && nextChar === '/') {
|
|
77
|
+
inLineComment = true
|
|
78
|
+
index += 1
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (char === '/' && nextChar === '*') {
|
|
83
|
+
inBlockComment = true
|
|
84
|
+
index += 1
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
result += char
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stripTrailingCommas(raw) {
|
|
95
|
+
let result = ''
|
|
96
|
+
let inString = false
|
|
97
|
+
let stringQuote = ''
|
|
98
|
+
let escaped = false
|
|
99
|
+
|
|
100
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
101
|
+
const char = raw[index]
|
|
102
|
+
|
|
103
|
+
if (inString) {
|
|
104
|
+
result += char
|
|
105
|
+
|
|
106
|
+
if (escaped) {
|
|
107
|
+
escaped = false
|
|
108
|
+
} else if (char === '\\') {
|
|
109
|
+
escaped = true
|
|
110
|
+
} else if (char === stringQuote) {
|
|
111
|
+
inString = false
|
|
112
|
+
stringQuote = ''
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (char === '"' || char === "'") {
|
|
119
|
+
inString = true
|
|
120
|
+
stringQuote = char
|
|
121
|
+
result += char
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (char === ',') {
|
|
126
|
+
const nextNonWhitespace = findNextNonWhitespace(raw, index + 1)
|
|
127
|
+
|
|
128
|
+
if (nextNonWhitespace === '}' || nextNonWhitespace === ']') {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
result += char
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function findNextNonWhitespace(raw, startIndex) {
|
|
140
|
+
for (let index = startIndex; index < raw.length; index += 1) {
|
|
141
|
+
const char = raw[index]
|
|
142
|
+
|
|
143
|
+
if (!/\s/u.test(char)) {
|
|
144
|
+
return char
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return undefined
|
|
21
149
|
}
|
|
22
150
|
|
|
23
151
|
export default getConfig
|