@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 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
- > AI 사용자용: 설치/셋업 가이드는 [`install.md`](./install.md), 사용 예시는 [`USAGE_EXAMPLES.md`](./USAGE_EXAMPLES.md)먼저 확인하세요.
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
- | 도구 | 총 룰 수 | type-aware | auto-fix | 플러그인 생태계 |
109
+ | 도구 | 총 룰 수 | 타입 기반 분석 | auto-fix | 플러그인 생태계 |
34
110
  |---|---|---|---|---|
35
111
  | **ESLint** | 700+ (+4000 플러그인) | ✅ 완전 | ✅ 우수 | 4000+ |
36
- | **Biome v2** | 423+ | v2 추가 | ✅ 양호 | 성장 중 |
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` | ✅ | off으로 설정 |
128
- | `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ | off으로 설정 |
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` | | Biome 내장 |
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` | ✅ | off으로 설정 |
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
- "nursery": {
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
- "style": {
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
- "nursery": {
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 @biomejs/biome check --write .
490
+ npx biome check --write .
491
+
492
+ # unsafe fix까지 적용해야 할 때만 수동 실행
493
+ npx biome check --write --unsafe .
405
494
 
406
495
  # CI (검사만, 수정 없음)
407
- npx @biomejs/biome ci .
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.11/schema.json",
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.1-dev.3.50",
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.11"
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
- // 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)
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