@hyeon/linter 11.0.1-dev.3.49 → 11.0.1
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 +81 -15
- package/biome.jsonc +10 -2
- package/package.json +1 -1
- package/src/index.mjs +134 -6
package/README.md
CHANGED
|
@@ -1,16 +1,75 @@
|
|
|
1
1
|
# @hyeon/linter
|
|
2
2
|
|
|
3
|
+
패키지 매니저에 맞춰 설치하세요.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
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을 사용해줘.
|
|
3
22
|
```bash
|
|
4
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
|
|
5
27
|
```
|
|
6
28
|
|
|
7
|
-
|
|
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
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
.vscode/settings.json에 아래를 추가해줘:
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"editor.defaultFormatter": "biomejs.biome",
|
|
48
|
+
"editor.formatOnSave": true,
|
|
49
|
+
"editor.codeActionsOnSave": {
|
|
50
|
+
"source.fixAll.biome": "explicit",
|
|
51
|
+
"source.organizeImports.biome": "explicit"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
.vscode/extensions.json에 아래를 추가해줘:
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"recommendations": ["biomejs.biome"]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
설치 후 biome check --write . 로 전체 포맷을 적용해줘.
|
|
64
|
+
````
|
|
65
|
+
|
|
8
66
|
|
|
9
67
|
## 지원 정책
|
|
10
68
|
|
|
11
|
-
- TypeScript 5.9+
|
|
69
|
+
- TypeScript 프로젝트는 TypeScript 5.9+ 기준
|
|
12
70
|
- Biome 기반 (lint + format + import organize 단일 도구)
|
|
13
71
|
- ESLint/Prettier 레거시 경로는 v11에서 완전 제거됨
|
|
72
|
+
- Tailwind CSS v4의 `@theme`, `@utility` 같은 CSS 지시문은 기본 설정에서 파싱 가능
|
|
14
73
|
|
|
15
74
|
## 왜 Biome인가
|
|
16
75
|
|
|
@@ -30,10 +89,10 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
30
89
|
|
|
31
90
|
### 룰 커버리지
|
|
32
91
|
|
|
33
|
-
| 도구 | 총 룰 수 |
|
|
92
|
+
| 도구 | 총 룰 수 | 타입 기반 분석 | auto-fix | 플러그인 생태계 |
|
|
34
93
|
|---|---|---|---|---|
|
|
35
94
|
| **ESLint** | 700+ (+4000 플러그인) | ✅ 완전 | ✅ 우수 | 4000+ |
|
|
36
|
-
| **Biome v2** | 423+ |
|
|
95
|
+
| **Biome v2** | 423+ | ⚠️ 일부 프로젝트 기반 분석 | ✅ 양호 | 성장 중 |
|
|
37
96
|
| **oxlint** | ~300 | ✅ tsgolint 43룰 | ⚠️ 제한적 | 최소 |
|
|
38
97
|
|
|
39
98
|
### import DX 커버리지 — Biome 선택 이유
|
|
@@ -124,8 +183,8 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
124
183
|
| `@ts/no-explicit-any` | `noExplicitAny` | ✅ | off으로 설정 |
|
|
125
184
|
| `@ts/no-unused-vars` | `noUnusedVariables` + `noUnusedFunctionParameters` | ✅ | `_` 접두사 패턴 지원 |
|
|
126
185
|
| `@ts/consistent-type-imports` | `useImportType` | ✅ | `inlineType` 스타일 지원 |
|
|
127
|
-
| `@ts/explicit-function-return-type` | `useExplicitType` | ✅ |
|
|
128
|
-
| `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ |
|
|
186
|
+
| `@ts/explicit-function-return-type` | `useExplicitType` | ✅ | nursery 규칙이라 기본 미활성화 |
|
|
187
|
+
| `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ | nursery 규칙이라 기본 미활성화 |
|
|
129
188
|
| `@ts/no-non-null-assertion` | `noNonNullAssertion` | ✅ | off으로 설정 |
|
|
130
189
|
| `@ts/no-empty-interface` | `noEmptyInterface` | ✅ | |
|
|
131
190
|
| `@ts/no-empty-object-type` | `noBannedTypes` | ✅ | |
|
|
@@ -150,6 +209,7 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
150
209
|
| `react-hooks/rules-of-hooks` | `useHookAtTopLevel` | ✅ | |
|
|
151
210
|
| `react-hooks/exhaustive-deps` | `useExhaustiveDependencies` | ✅ | |
|
|
152
211
|
| `react-refresh/only-export-components` | `useComponentExportOnlyModules` | ✅ | |
|
|
212
|
+
| `react/jsx-curly-brace-presence` | `useConsistentCurlyBraces` | ✅ | 문자열 JSX props/children은 중괄호 없이 작성 |
|
|
153
213
|
| `react/display-name` | — | ❌ | Biome 미구현 |
|
|
154
214
|
| `react/no-unescaped-entities` | — | ❌ | Biome 미구현 |
|
|
155
215
|
|
|
@@ -166,7 +226,7 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
166
226
|
| ↳ `arrowParens: always` | `arrowParentheses: "always"` | ✅ | |
|
|
167
227
|
| ↳ `jsxSingleQuote: false` | `jsxQuoteStyle: "double"` | ✅ | |
|
|
168
228
|
| ↳ `@trivago/sort-imports` | `organizeImports` | ✅ | 그룹/정렬/병합 모두 지원 |
|
|
169
|
-
| ↳ `prettier-plugin-tailwindcss` | `useSortedClasses` |
|
|
229
|
+
| ↳ `prettier-plugin-tailwindcss` | `useSortedClasses` | ⚠️ | Biome 내장 규칙이지만 unsafe fix라 기본 미활성화 |
|
|
170
230
|
| `arrow-body-style` | — | ❌ | Biome 미구현 |
|
|
171
231
|
| `prefer-arrow-callback` | `useArrowFunction` | ✅ | |
|
|
172
232
|
| `curly` | `useBlockStatements` | ✅ | |
|
|
@@ -185,7 +245,7 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
185
245
|
| `import-x/no-duplicates` | `organizeImports` 자동 merge | ✅ | |
|
|
186
246
|
| `import-x/no-unresolved` | — | ➖ | 원래 off |
|
|
187
247
|
| `no-empty-pattern` | `noEmptyPattern` | ✅ | off으로 설정 |
|
|
188
|
-
| `camelcase` | `useNamingConvention` | ✅ |
|
|
248
|
+
| `camelcase` | `useNamingConvention` | ✅ | 기본 미활성화 |
|
|
189
249
|
| `class-methods-use-this` | — | ❌ | Biome 미구현 |
|
|
190
250
|
| `no-multi-spaces` | formatter | ✅ | |
|
|
191
251
|
|
|
@@ -219,6 +279,9 @@ npm install --save-dev @hyeon/linter @biomejs/biome
|
|
|
219
279
|
|
|
220
280
|
```bash
|
|
221
281
|
npm install --save-dev @hyeon/linter @biomejs/biome
|
|
282
|
+
pnpm add -D @hyeon/linter @biomejs/biome
|
|
283
|
+
yarn add -D @hyeon/linter @biomejs/biome
|
|
284
|
+
bun add -d @hyeon/linter @biomejs/biome
|
|
222
285
|
```
|
|
223
286
|
|
|
224
287
|
### 공통 scripts
|
|
@@ -310,9 +373,12 @@ TS + React 규칙이 모두 자동 적용됩니다. ESLint에서는 5개 프리
|
|
|
310
373
|
"extends": ["@hyeon/linter/biome-config"],
|
|
311
374
|
"linter": {
|
|
312
375
|
"rules": {
|
|
313
|
-
"
|
|
314
|
-
// Next.js 전용 규칙 활성화
|
|
315
|
-
"noImgElement": "error"
|
|
376
|
+
"performance": {
|
|
377
|
+
// Next.js 전용 이미지 최적화 규칙 활성화
|
|
378
|
+
"noImgElement": "error"
|
|
379
|
+
},
|
|
380
|
+
"style": {
|
|
381
|
+
// Next.js 전용 head 요소 규칙 활성화
|
|
316
382
|
"noHeadElement": "error"
|
|
317
383
|
}
|
|
318
384
|
}
|
|
@@ -337,7 +403,7 @@ TS + React 규칙이 모두 자동 적용됩니다. ESLint에서는 5개 프리
|
|
|
337
403
|
"extends": ["@hyeon/linter/biome-config"],
|
|
338
404
|
"linter": {
|
|
339
405
|
"rules": {
|
|
340
|
-
"
|
|
406
|
+
"complexity": {
|
|
341
407
|
// NestJS 데코레이터 패턴에서 빈 클래스 허용
|
|
342
408
|
"noUselessConstructor": "off"
|
|
343
409
|
}
|
|
@@ -375,7 +441,7 @@ monorepo/
|
|
|
375
441
|
"extends": ["../../biome.jsonc"],
|
|
376
442
|
"linter": {
|
|
377
443
|
"rules": {
|
|
378
|
-
"
|
|
444
|
+
"performance": {
|
|
379
445
|
"noImgElement": "error"
|
|
380
446
|
}
|
|
381
447
|
}
|
|
@@ -401,10 +467,10 @@ monorepo/
|
|
|
401
467
|
|
|
402
468
|
```bash
|
|
403
469
|
# lint + format + import 정리 한 번에
|
|
404
|
-
npx
|
|
470
|
+
npx biome check --write .
|
|
405
471
|
|
|
406
472
|
# CI (검사만, 수정 없음)
|
|
407
|
-
npx
|
|
473
|
+
npx biome ci .
|
|
408
474
|
```
|
|
409
475
|
|
|
410
476
|
## v10 → v11 마이그레이션
|
package/biome.jsonc
CHANGED
|
@@ -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
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
|