@hyeon/linter 11.0.1-dev.3.50 → 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 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
- > AI 사용자용: 설치/셋업 가이드는 [`install.md`](./install.md), 사용 예시는 [`USAGE_EXAMPLES.md`](./USAGE_EXAMPLES.md)를 먼저 확인하세요.
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
- | 도구 | 총 룰 수 | type-aware | auto-fix | 플러그인 생태계 |
92
+ | 도구 | 총 룰 수 | 타입 기반 분석 | auto-fix | 플러그인 생태계 |
34
93
  |---|---|---|---|---|
35
94
  | **ESLint** | 700+ (+4000 플러그인) | ✅ 완전 | ✅ 우수 | 4000+ |
36
- | **Biome v2** | 423+ | v2 추가 | ✅ 양호 | 성장 중 |
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` | ✅ | off으로 설정 |
128
- | `@ts/explicit-module-boundary-types` | `useExplicitType` | ✅ | off으로 설정 |
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` | | Biome 내장 |
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` | ✅ | off으로 설정 |
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
- "nursery": {
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
- "style": {
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
- "nursery": {
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 @biomejs/biome check --write .
470
+ npx biome check --write .
405
471
 
406
472
  # CI (검사만, 수정 없음)
407
- npx @biomejs/biome ci .
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyeon/linter",
3
- "version": "11.0.1-dev.3.50",
3
+ "version": "11.0.1",
4
4
  "description": "Biome-based lint, format, and import organize configuration",
5
5
  "main": "./src/index.mjs",
6
6
  "type": "module",
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