@3o3/eslint-config 1.0.0 → 2.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/ADOPTION.md +104 -0
- package/README.md +138 -9
- package/package.json +23 -8
- package/src/all.js +2 -0
- package/src/base.js +6 -103
- package/src/core.js +67 -0
- package/src/house.js +36 -0
- package/src/index.js +17 -37
- package/src/plugin.js +36 -0
- package/src/react.js +39 -26
- package/src/rules/no-app-event-on-window.js +123 -0
- package/src/tests.js +9 -5
- package/src/type-checked.js +26 -0
package/ADOPTION.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 다른 레포에 @3o3/eslint-config 적용하기
|
|
2
|
+
|
|
3
|
+
자체 ESLint 설정을 쓰던 레포를 `@3o3/eslint-config` 2.x 기반으로 바꾸는 절차다. 설정을 교체하고, 새로 보고되는 것을 그 자리에서 해결해 lint 가 통과하는 상태까지 한 번에 간다.
|
|
4
|
+
|
|
5
|
+
새로 보고되는 것의 대부분은 `eslint --fix` 로 고쳐진다(`array-type`, import 정렬, `import type`, 중복 import, `no-implicit-coercion` 등). 직접 고칠 것은 그 뒤에 남는 것뿐이다.
|
|
6
|
+
|
|
7
|
+
아래 프롬프트를 코딩 에이전트에게 그대로 넘기면 된다.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
이 레포의 ESLint 설정을 @3o3/eslint-config 2.x 기반으로 바꾸고, 새 설정에서 lint 가 통과하도록 만들어 줘.
|
|
11
|
+
"통과"의 기준은 이 레포가 지금 쓰는 기준 그대로다(lint 명령의 --max-warnings 값, 경고로 낮추는 설정 등은 바꾸지 않는다).
|
|
12
|
+
|
|
13
|
+
지킬 것:
|
|
14
|
+
- 코드의 동작을 바꾸지 않는다. 고치면 동작이 달라질 수 있는 것은 고치지 말고 보고한다(6번의 목록 참고).
|
|
15
|
+
- eslint-disable 주석을 새로 추가하지 않는다. 규칙을 임의로 끄거나 완화하지 않는다. 그래야 할 것 같으면 바꾸지 말고 보고한다.
|
|
16
|
+
- 레포 고유 설정(무시 경로, 앱별 규칙, 이 패키지에 없는 플러그인, 경고로 낮추는 설정)은 그대로 유지한다.
|
|
17
|
+
|
|
18
|
+
0. 현황을 기록한다.
|
|
19
|
+
- 패키지 매니저, ESLint 설정 파일 위치(모노레포면 전부), lint 를 실행하는 명령.
|
|
20
|
+
- 지금 설정으로 lint 를 돌려 오류·경고 수를 적어 둔다.
|
|
21
|
+
|
|
22
|
+
1. 의존성을 바꾼다. (이 레포의 패키지 매니저로)
|
|
23
|
+
- 추가: @3o3/eslint-config@^2.0.0
|
|
24
|
+
- eslint 는 9 또는 10 이면 된다. 버전을 올릴지는 바꾸지 말고 보고한다.
|
|
25
|
+
- 제거(있으면): eslint-plugin-react, eslint-plugin-import, @emotion/eslint-plugin, eslint-import-resolver-node
|
|
26
|
+
- 설정 파일에서 직접 import 하던 @eslint/js, typescript-eslint, eslint-plugin-react-hooks, eslint-plugin-simple-import-sort, eslint-config-prettier 는 2번을 끝낸 뒤 더 쓰이지 않으면 제거한다.
|
|
27
|
+
|
|
28
|
+
2. 설정 파일을 아래 구조로 바꾼다. 모노레포에서 패키지마다 설정이 있으면 레이어 조합은 공용 설정 패키지(있다면)에 한 번만 만들고 각 패키지가 그것을 쓰게 한다. 패키지별 설정 파일에 남는 규칙도 3번의 이름 이관 대상이다.
|
|
29
|
+
|
|
30
|
+
import { base, react, typeChecked, tests, prettier } from '@3o3/eslint-config'
|
|
31
|
+
|
|
32
|
+
export default [
|
|
33
|
+
{ ignores: [/* 기존 무시 경로 그대로 + 타입 정보 없이 두던 파일(예: '**/*.config.*') */] },
|
|
34
|
+
...base,
|
|
35
|
+
...react, // React 를 쓰지 않으면 뺀다
|
|
36
|
+
...typeChecked, // 타입 인지 규칙(no-floating-promises, no-misused-promises, no-unsafe-* 등). 기존 설정이 tseslint.configs.recommendedTypeChecked·strictTypeChecked 나 parserOptions.project·projectService 를 쓰지 않았으면 뺀다
|
|
37
|
+
...tests, // __tests__/**, *.spec.*, *.test.* 에 적용된다
|
|
38
|
+
// 레포 고유 설정: 기존 플러그인(next, storybook, tanstack, a11y 등), 앱별 규칙, 경고로 낮추는 설정을 여기에 그대로 둔다
|
|
39
|
+
prettier,
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
- 이 패키지의 레이어는 .ts·.tsx·.mts·.cts 에만 적용된다. 기존 설정이 .js 등도 lint 했으면 그 부분은 레포 고유 설정으로 남긴다.
|
|
43
|
+
- 남기는 JS 용 설정(js.configs.recommended 등)에는 files: ['**/*.{js,mjs,cjs}'] 를 붙인다. files 없이 @3o3 레이어 뒤에 두면 TS 파일에서 no-undef 같은 규칙이 다시 켜진다.
|
|
44
|
+
- @3o3 레이어가 등록하는 플러그인(@typescript-eslint, @eslint-react, react-hooks, import-x, simple-import-sort)을 설정에서 다시 등록하지 않는다. 그 플러그인의 preset(tseslint.configs.*, reactHooks.configs.* 등)을 덧붙이지도 않는다. 레포에 설치된 사본이 이 패키지가 쓰는 것과 다르면 "Cannot redefine plugin" 오류로 실행되지 않는다. 그 플러그인의 규칙을 더 켜거나 옵션을 바꾸려면 rules 에만 쓴다.
|
|
45
|
+
- 기존 설정이 preset 의 규칙 맵을 펼쳐 쓰던 경우(예: ...reactHooks.configs.recommended.rules)는 그 preset 이 켜던 규칙을 이름으로 나열해 rules 로 옮기고 보고한다.
|
|
46
|
+
- 기존 설정이 켜던 규칙 중 @3o3 레이어에 없는 것(예: no-console, import-x/no-cycle)은 레포 고유 설정에 rules 로 옮겨 유지한다. (react/*·import/*·@emotion/* 는 3번을 따른다)
|
|
47
|
+
- 기존 설정이 @3o3 레이어와 똑같이 설정하던 규칙은 지운다. 같은 규칙을 다른 옵션으로 설정하던 것은 그대로 두고 보고한다.
|
|
48
|
+
|
|
49
|
+
3. 규칙 이름을 옮긴다. 설정 파일과 소스의 eslint-disable 계열 주석 모두 해당한다.
|
|
50
|
+
- import/<규칙> → import-x/<규칙>. settings 의 import/* 키도 import-x/* 로. (import/enforce-node-protocol-usage 는 대응 규칙이 없으니 지우고 보고한다)
|
|
51
|
+
- react/<규칙> → 아래 대응표. 대응표에 없는 것은 지우고 보고한다. 주석에서는 그 규칙 이름만 빼고, 한 규칙이 여러 개로 나뉜 경우 그 줄에서 실제로 보고되는 것만 남긴다.
|
|
52
|
+
- @emotion/<규칙> → 지운다.
|
|
53
|
+
- react-hooks/* 는 그대로 둔다.
|
|
54
|
+
|
|
55
|
+
4. lint 를 돌려 실행 실패를 0건으로 만든다.
|
|
56
|
+
- "Could not find plugin", "Definition for rule ... not found", "Cannot redefine plugin" 이 없어야 한다.
|
|
57
|
+
- "Parsing error ... was not found by the project service" 는 그 파일이 tsconfig 에 포함되지 않았다는 뜻이다. 설정 파일·스크립트처럼 원래 타입 검사 대상이 아닌 파일이면 ignores 에 넣거나 parserOptions.projectService.allowDefaultProject 에 넣는다. tsconfig 에 포함된 파일인데도 이 오류가 나면 tsconfig 가 lint 를 실행한 디렉터리보다 위에 있는 경우다. tsconfig 가 있는 디렉터리의 설정 파일에 parserOptions.tsconfigRootDir: import.meta.dirname 을 둔다. 그 밖의 경우는 고치지 말고 보고한다.
|
|
58
|
+
- 설정 파일에 'import/', 'react/', '@emotion/ 문자열이 남아 있지 않은지 검색한다. (off 로 끈 옛 규칙과 옛 import/* settings 는 오류 없이 무시되어 lint 로는 드러나지 않는다)
|
|
59
|
+
- 여기까지를 커밋한다(설정 교체).
|
|
60
|
+
|
|
61
|
+
5. eslint --fix 를 돌린다.
|
|
62
|
+
- 자동 수정 결과만 따로 커밋한다. 파일이 많이 바뀌므로 리뷰할 때 구분할 수 있어야 한다.
|
|
63
|
+
- 수정 후 타입 검사와 테스트를 돌려 통과하는지 확인한다.
|
|
64
|
+
|
|
65
|
+
6. 남은 보고를 직접 고친다. 규칙별로 묶어서 처리하고, 고칠 때 코드의 동작이 바뀌면 안 된다.
|
|
66
|
+
아래에 해당하면 고치지 말고 파일·줄·이유를 목록으로 보고한다.
|
|
67
|
+
- 고치면 동작이 달라질 수 있는 것 (예: no-floating-promises 에 await 를 붙이면 실행 순서가 바뀜, exhaustive-deps 에 의존성을 추가하면 effect 가 더 자주 실행됨, eqeqeq 로 바꾸면 비교 결과가 달라지는 경우)
|
|
68
|
+
- 타입을 새로 설계해야 하는 것 (no-unsafe-*, no-explicit-any 가 외부 응답 타입에서 나오는 경우 등)
|
|
69
|
+
- 한 앱·한 디렉터리에 몰려 있어 설정 문제로 보이는 것 (예: import-x/no-unresolved 가 경로 별칭에서 한꺼번에 나옴 → resolver 설정을 확인하고 보고한다)
|
|
70
|
+
- @3o3/no-app-event-on-window 의 자동 수정은 @3o3/app-bridge 0.18.0 이상이 필요하다. 그보다 낮으면 수정하지 말고 보고한다.
|
|
71
|
+
- 규칙이 이 코드에 맞지 않아 보이는 것
|
|
72
|
+
|
|
73
|
+
7. 검증한다.
|
|
74
|
+
- 레포의 lint 명령, 타입 검사, 테스트를 돌린다.
|
|
75
|
+
- lint 가 통과하지 않으면 6번의 보고 목록에 있는 것만 남았는지 확인한다.
|
|
76
|
+
|
|
77
|
+
react 규칙 대응표:
|
|
78
|
+
- react/jsx-key → @eslint-react/no-missing-key
|
|
79
|
+
- react/jsx-no-comment-textnodes → @eslint-react/jsx-no-comment-textnodes
|
|
80
|
+
- react/jsx-no-target-blank → @eslint-react/dom-no-unsafe-target-blank
|
|
81
|
+
- react/no-children-prop → @eslint-react/jsx-no-children-prop
|
|
82
|
+
- react/no-danger-with-children → @eslint-react/dom-no-dangerously-set-innerhtml-with-children
|
|
83
|
+
- react/no-direct-mutation-state → @eslint-react/no-direct-mutation-state
|
|
84
|
+
- react/no-find-dom-node → @eslint-react/dom-no-find-dom-node
|
|
85
|
+
- react/no-render-return-value → @eslint-react/dom-no-render-return-value
|
|
86
|
+
- react/no-unknown-property → @eslint-react/dom-no-unknown-property
|
|
87
|
+
- react/no-deprecated → @eslint-react/no-component-will-mount, @eslint-react/no-component-will-receive-props, @eslint-react/no-component-will-update, @eslint-react/dom-no-render, @eslint-react/dom-no-hydrate
|
|
88
|
+
- 대응 규칙 없음(지운다): react/no-unescaped-entities, react/no-string-refs, react/no-is-mounted, react/jsx-no-duplicate-props, react/jsx-no-undef, react/require-render-return
|
|
89
|
+
- @3o3 레이어가 켜지 않는 규칙(지운다): react/display-name, react/prop-types, react/react-in-jsx-scope, react/jsx-uses-react, react/jsx-uses-vars
|
|
90
|
+
|
|
91
|
+
끝나면 아래를 정리해서 알려 줘.
|
|
92
|
+
- 바꾼 설정 파일과 의존성
|
|
93
|
+
- 0번에서 기록한 이전 결과와 지금 결과(오류·경고 수)
|
|
94
|
+
- 자동 수정으로 바뀐 파일 수, 직접 고친 것의 규칙별 수
|
|
95
|
+
- 지운 규칙, 유지하려고 레포 고유 설정으로 옮긴 규칙
|
|
96
|
+
- 고치지 않고 남긴 것(6번 목록)과 판단이 필요한 것
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 알아둘 것
|
|
100
|
+
|
|
101
|
+
- **버전**: `@3o3/eslint-config` 2.0.0 이상, `eslint` 9 또는 10, Node 20.19.0 이상(`docs` 레이어를 쓰면 22.13 이상).
|
|
102
|
+
- **한 번에 끝내기 어려우면** 프롬프트의 `base` 를 `core` 로 바꿔 넘기고, 나중에 `house` 를 얹는다. `house` 는 `array-type`, import 정렬 같은 스타일 규칙이고 대부분 `eslint --fix` 로 고쳐진다.
|
|
103
|
+
- **에이전트가 고치지 않고 남긴 것**은 사람이 판단한다. 동작이 달라질 수 있는 수정, 타입 설계가 필요한 수정, 규칙을 끌지 여부가 여기에 해당한다.
|
|
104
|
+
- **1.x 를 쓰던 레포**는 README 의 "1.x 에서 2.0 으로 올리기"를 따른다.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @3o3/eslint-config
|
|
2
2
|
|
|
3
|
-
3o3 공유 ESLint 설정(
|
|
3
|
+
3o3 공유 ESLint 설정(flat config). **사용처에 따라 레이어를 골라** 쓴다.
|
|
4
4
|
|
|
5
5
|
## 설치
|
|
6
6
|
|
|
@@ -8,20 +8,102 @@
|
|
|
8
8
|
pnpm add -D @3o3/eslint-config eslint typescript
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`eslint` 와 `typescript` 는 peerDependency, 나머지 플러그인(typescript-eslint,
|
|
12
|
-
eslint-
|
|
11
|
+
`eslint`(`^9 || ^10`) 와 `typescript` 는 peerDependency, 나머지 플러그인(typescript-eslint,
|
|
12
|
+
@eslint-react, react-hooks, import-x, jsdoc, tsdoc, prettier)은 이
|
|
13
13
|
패키지의 dependency 라 따로 설치할 필요가 없다.
|
|
14
14
|
|
|
15
|
+
### import 규칙
|
|
16
|
+
|
|
17
|
+
import 규칙은 `eslint-plugin-import-x` 의 것이고 이름도 `import-x/*` 다. settings 도 `import-x/*`(`import-x/resolver` 등)를 쓴다. 규칙을 더 켤 때는 `rules` 에 `import-x/*` 이름으로 직접 켠다.
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { base } from '@3o3/eslint-config'
|
|
21
|
+
|
|
22
|
+
export default [...base, { rules: { 'import-x/no-cycle': 'error' } }]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 1.x 에서 2.0 으로 올리기
|
|
26
|
+
|
|
27
|
+
2.0 은 ESLint 10 을 지원하지 않는 플러그인 세 개를 교체했다. 규칙 이름이 바뀌므로 설정 파일과 `eslint-disable` 주석을 고쳐야 한다(ESLint 9 에 머물러도 같다). `base`(와 `all`)가 켜는 규칙도 바뀐다: recommended 를 끄거나 낮추던 설정 6개를 없애고, 스타일 규칙을 서비스 레포와 함께 쓸 수 있는 것으로 바꿨다.
|
|
28
|
+
|
|
29
|
+
| 1.x | 2.0 | 고치지 않으면 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `react/*` 규칙 | `@eslint-react/*` (아래 프롬프트의 대응표) | 설정에서 켠 규칙은 `Could not find plugin "react"` 로 실행 실패 |
|
|
32
|
+
| `import/*` 규칙 | `import-x/*` | 설정에서 켠 규칙은 `Could not find plugin "import"` 로 실행 실패 |
|
|
33
|
+
| `import/*` settings | `import-x/*` | 오류 없이 **무시된다** |
|
|
34
|
+
| `@emotion/*` 규칙 | 없음 (플러그인을 뺐다) | 설정에서 켠 규칙은 `Could not find plugin "@emotion"` 로 실행 실패 |
|
|
35
|
+
| `base` 가 끄거나 낮추던 규칙 6개 (`import-x/no-duplicates`, `import-x/export`, `no-empty-object-type`, `no-require-imports`, `no-non-null-asserted-optional-chain`, `no-unused-vars` 의 catch 바인딩) | recommended 수준 | 기존 코드에서 새로 보고될 수 있다. `no-duplicates` 는 `eslint --fix` 로 고쳐지고, 쓰지 않는 `catch (e)` 는 `catch {` 로 바꾼다 |
|
|
36
|
+
| 새로 켜지는 스타일 규칙 (`consistent-type-imports`, `simple-import-sort/imports`·`exports`) | `eslint --fix` 로 고친다 | 기존 코드에서 error 로 보고된다. `typeof import('x')` 타입 표기는 `import type * as X` 로 직접 바꾼다 |
|
|
37
|
+
| 빠지는 규칙 (`naming-convention`, `member-ordering`, `no-non-null-assertion`, `no-warning-comments`, `curly`) | 계속 쓰려면 `rules` 에 직접 켠다 | 검사가 없어지고, 그 규칙의 `eslint-disable` 주석은 "쓰이지 않는 eslint-disable" 경고가 된다 |
|
|
38
|
+
| 위 규칙들의 `eslint-disable` 주석 | 새 이름으로 | "규칙 정의를 찾을 수 없다" 오류로 보고되고, 새 규칙은 억제하지 못한다 |
|
|
39
|
+
|
|
40
|
+
`off` 로 끈 옛 규칙(`react/*`·`import/*`·`@emotion/*`)은 세 플러그인 모두 오류 없이 무시되니, 설정 파일에서 직접 찾아 지워야 한다. `react-hooks/*` 는 이름과 동작이 그대로다.
|
|
41
|
+
|
|
42
|
+
### 마이그레이션 프롬프트
|
|
43
|
+
|
|
44
|
+
아래를 코딩 에이전트에게 그대로 넘기면 된다.
|
|
45
|
+
|
|
46
|
+
````text
|
|
47
|
+
이 레포의 @3o3/eslint-config 를 1.x 에서 2.0 으로 올리는 마이그레이션을 해 줘.
|
|
48
|
+
2.0 은 플러그인을 교체해서 규칙 이름이 바뀌었고, 켜지는 스타일 규칙도 바뀌었다. 코드 동작은 건드리지 않는다. 바꿔도 되는 것은 ESLint 설정, eslint-disable 주석, 그리고 `eslint --fix` 가 자동으로 고치는 것(import 정렬 등)뿐이다.
|
|
49
|
+
|
|
50
|
+
1. ESLint 설정 파일(eslint.config.*)을 고친다.
|
|
51
|
+
- `import/` 로 시작하는 규칙 이름과 settings 키를 `import-x/` 로 바꾼다. (예: `import/order` → `import-x/order`, `import/resolver` → `import-x/resolver`)
|
|
52
|
+
- `import/enforce-node-protocol-usage` 는 대응 규칙이 없으니 지우고 보고한다.
|
|
53
|
+
- `react/` 로 시작하는 규칙은 아래 대응표대로 바꾼다. 대응표에 없는 `react/*` 규칙은 지우고 보고한다. `off` 로 끈 것도 지운다.
|
|
54
|
+
- `@emotion/` 로 시작하는 규칙은 지운다.
|
|
55
|
+
- `eslint-plugin-react`, `eslint-plugin-import`, `@emotion/eslint-plugin`, `eslint-plugin-simple-import-sort` 를 직접 등록하거나 그 preset(`flatConfigs.*` 등)을 덧붙인 부분은 지운다. 그 preset 으로 켜던 규칙이 필요하면 `rules` 에 새 이름으로 직접 켜고 보고한다.
|
|
56
|
+
- `react-hooks/*` 는 건드리지 않는다.
|
|
57
|
+
|
|
58
|
+
2. 소스의 eslint-disable 계열 주석(`eslint-disable`, `eslint-disable-line`, `eslint-disable-next-line`, `eslint-enable`)을 고친다.
|
|
59
|
+
- `import/<규칙>` → `import-x/<규칙>`
|
|
60
|
+
- `react/<규칙>` → 대응표의 새 이름. 한 규칙이 여러 개로 나뉜 경우 그 줄에서 실제로 보고되는 것만 남긴다. 대응 규칙이 없으면 주석에서 그 규칙을 뺀다.
|
|
61
|
+
- `@emotion/<규칙>` → 주석에서 그 규칙을 뺀다.
|
|
62
|
+
- 2.0 에서 빠진 규칙(`@typescript-eslint/naming-convention`, `@typescript-eslint/member-ordering`, `@typescript-eslint/no-non-null-assertion`, `no-warning-comments`, `curly`)의 주석은 지운다. 설정에서 이 규칙을 직접 켜 두었으면 그대로 둔다.
|
|
63
|
+
|
|
64
|
+
3. 검증한다.
|
|
65
|
+
- 먼저 `eslint --fix` 를 돌린다. 2.0 이 새로 켜는 import 정렬·`import type`·중복 import 는 자동으로 고쳐진다. 이 자동 수정 결과는 커밋에 포함한다.
|
|
66
|
+
- 레포의 lint 명령을 돌린다.
|
|
67
|
+
- "Could not find plugin", "Definition for rule ... not found", 쓰이지 않는 eslint-disable 경고가 0건이어야 한다.
|
|
68
|
+
- ESLint 설정 파일에 `'import/`, `'react/`, `'@emotion/` 문자열이 남아 있지 않은지 검색한다. (`off` 로 끈 옛 규칙과 옛 `import/*` settings 는 오류 없이 무시되어 lint 로는 드러나지 않는다)
|
|
69
|
+
- 새로 보고되는 lint 오류는 eslint-disable 을 추가해서 덮지 말고, 파일·줄·규칙을 목록으로 보고한다.
|
|
70
|
+
|
|
71
|
+
react 규칙 대응표:
|
|
72
|
+
- react/jsx-key → @eslint-react/no-missing-key
|
|
73
|
+
- react/jsx-no-comment-textnodes → @eslint-react/jsx-no-comment-textnodes
|
|
74
|
+
- react/jsx-no-target-blank → @eslint-react/dom-no-unsafe-target-blank
|
|
75
|
+
- react/no-children-prop → @eslint-react/jsx-no-children-prop
|
|
76
|
+
- react/no-danger-with-children → @eslint-react/dom-no-dangerously-set-innerhtml-with-children
|
|
77
|
+
- react/no-direct-mutation-state → @eslint-react/no-direct-mutation-state
|
|
78
|
+
- react/no-find-dom-node → @eslint-react/dom-no-find-dom-node
|
|
79
|
+
- react/no-render-return-value → @eslint-react/dom-no-render-return-value
|
|
80
|
+
- react/no-unknown-property → @eslint-react/dom-no-unknown-property
|
|
81
|
+
- react/no-deprecated → @eslint-react/no-component-will-mount, @eslint-react/no-component-will-receive-props, @eslint-react/no-component-will-update, @eslint-react/dom-no-render, @eslint-react/dom-no-hydrate
|
|
82
|
+
- 대응 규칙 없음(지운다): react/no-unescaped-entities, react/no-string-refs, react/no-is-mounted, react/jsx-no-duplicate-props, react/jsx-no-undef, react/require-render-return
|
|
83
|
+
|
|
84
|
+
끝나면 바꾼 파일 수, 지운 규칙, 새로 보고되는 lint 오류를 정리해서 알려 줘.
|
|
85
|
+
````
|
|
86
|
+
|
|
15
87
|
## 레이어
|
|
16
88
|
|
|
17
89
|
| 레이어 | 내용 | 언제 |
|
|
18
90
|
|---|---|---|
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
91
|
+
| `core` | js + ts 정합성 규칙(`@eslint/js` + `typescript-eslint` + `import-x` 의 recommended 그대로) + 사내 규칙 | 3o3 하우스 규칙을 나중에 도입할 때 `base` 대신 |
|
|
92
|
+
| `house` | 공통 스타일 규칙(`eqeqeq` / `no-implicit-coercion` / `array-type` / `no-inferrable-types` / `consistent-type-imports` / `simple-import-sort`). 대부분 `eslint --fix` 로 고쳐진다 | `core` 위에 |
|
|
93
|
+
| `base` | `core + house` | 모든 TS 코드(토대) |
|
|
94
|
+
| `typeChecked` | 타입 정보를 쓰는 규칙(`no-floating-promises` / `no-misused-promises` / `no-unsafe-*` 등 23개) | 타입 인지 lint 를 쓸 때 `core` 또는 `base` 위에 |
|
|
95
|
+
| `react` | `base` 위 React 델타(`@eslint-react` + `react-hooks`) | React 패키지/앱 |
|
|
96
|
+
| `tests` | 테스트 파일(`**/__tests__/**`, `**/*.{spec,test}.*`)에서 `array-type`·`no-non-null-assertion` off | `base` 와 레포 고유 규칙 뒤에 |
|
|
21
97
|
| `docs` | 공개 API 문서 완전성(`jsdoc` + `tsdoc`) — `src/**` 글롭, 내부는 파일 지시어로 opt-out | 공개 API 문서 강제 |
|
|
22
98
|
| `all` | `base + react + prettier` 번들 | "전부 켜기" |
|
|
23
99
|
| `prettier` | `eslint-config-prettier`(포매팅 충돌 규칙 off) | 항상 **마지막**에 스프레드 |
|
|
24
100
|
|
|
101
|
+
`core`(따라서 `base` 도)는 자체 규칙 `@3o3/no-app-event-on-window`(error)를 켠다. 앱 이벤트(`flutter_*`)를 `window.addEventListener`·`removeEventListener` 에 문자열 리터럴로 직접 넘기는 코드를 막는다. `onBackPressed` 같은 등록형 이벤트는 직접 붙이면 앱에 등록되지 않아 동작하지 않으므로, 앱 이벤트 전체를 `@3o3/app-bridge` 의 `webView` 로 모은다. `eslint --fix` 가 `webView.addEventListener`·`removeEventListener` 로 바꾸고 import 를 채운다. `webView` 라는 이름을 이미 다른 값에 쓰는 파일은 알리기만 한다.
|
|
102
|
+
|
|
103
|
+
`house` 는 `no-unused-vars` 의 옵션도 정한다: rest 로 나머지를 받으려고 꺼낸 프로퍼티(`const { a, ...rest } = x`)와 `_` 로 시작하는 인자는 허용한다. `core` 만 쓰면 이것도 보고된다.
|
|
104
|
+
|
|
105
|
+
레이어는 `.ts`·`.tsx`·`.mts`·`.cts` 파일에 적용된다(`docs` 레이어는 `.ts`·`.tsx`). `.js` 등은 대상이 아니다.
|
|
106
|
+
|
|
25
107
|
`react` 는 `base` 위에 얹는 누적 델타다. 각 영역의 3o3 규칙을 이미 포함한다.
|
|
26
108
|
전역 무시(dist/esm/비 ts·tsx)와 프로젝트 고유 규칙은 소비하는 쪽 config 에서 직접 둔다.
|
|
27
109
|
|
|
@@ -29,8 +111,8 @@ eslint-plugin-react, react-hooks, import, @emotion, jsdoc, tsdoc, prettier)은
|
|
|
29
111
|
|
|
30
112
|
루트에 `eslint.config.mjs` 하나를 두는 **단일 설정**을 권장한다(이 모노레포도 동일).
|
|
31
113
|
`defineConfig` + 문자열 `extends` 스타일(ESLint 가 미는 방식)을 기본으로 쓴다.
|
|
32
|
-
default export 는 `configs` 맵을 가진 플러그인 형태(typescript-eslint
|
|
33
|
-
|
|
114
|
+
default export 는 `configs` 맵을 가진 플러그인 형태(typescript-eslint 와 동일)라
|
|
115
|
+
`extends: ['@3o3/<layer>']` 로 참조된다.
|
|
34
116
|
|
|
35
117
|
```js
|
|
36
118
|
// eslint.config.mjs (React 포함 레포)
|
|
@@ -77,6 +159,53 @@ export default [
|
|
|
77
159
|
]
|
|
78
160
|
```
|
|
79
161
|
|
|
162
|
+
### typeChecked 레이어
|
|
163
|
+
|
|
164
|
+
`typeChecked` 는 `parserOptions.projectService` 를 켠다. lint 대상 파일이 가장 가까운 `tsconfig.json` 에 포함돼 있어야 하고, 타입 정보 없이 돌 때보다 느리다. 그래서 `base` 에 넣지 않고 따로 얹는다.
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
import { base, react, typeChecked, prettier } from '@3o3/eslint-config'
|
|
168
|
+
|
|
169
|
+
export default [
|
|
170
|
+
...base,
|
|
171
|
+
...react,
|
|
172
|
+
...typeChecked,
|
|
173
|
+
{
|
|
174
|
+
languageOptions: {
|
|
175
|
+
parserOptions: {
|
|
176
|
+
// tsconfig 를 찾기 시작할 위치(이 설정 파일이 있는 디렉터리). 없으면 ESLint 를 실행한 디렉터리가 기준이 된다
|
|
177
|
+
tsconfigRootDir: import.meta.dirname,
|
|
178
|
+
// tsconfig 에 포함되지 않은 파일(설정 파일 등). typeChecked 의 projectService: true 를 이 객체가 대체한다
|
|
179
|
+
projectService: { allowDefaultProject: ['*.config.{ts,mts}'] },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
// 레포 고유 규칙은 typeChecked 뒤에 둔다
|
|
184
|
+
prettier,
|
|
185
|
+
]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
- **`tsconfigRootDir` 를 넣는다.** 넣지 않으면 실행한 디렉터리 위쪽의 tsconfig 를 찾지 못한다. tsconfig 가 레포 루트에만 있는데 하위 디렉터리에서 ESLint 를 실행하면 모든 파일이 `was not found by the project service` 파싱 오류가 된다.
|
|
189
|
+
- **tsconfig 에 포함되지 않은 `.ts` 파일은 파일마다 파싱 오류가 난다.** `allowDefaultProject` 에 넣거나, 그 파일들에 `typescript-eslint` 의 `disableTypeChecked` 를 적용한다.
|
|
190
|
+
- **레포 고유 규칙은 `typeChecked` 뒤에 둔다.** `typeChecked` 가 `no-var`·`prefer-const` 등 일부 기본 규칙을 다시 설정하므로, `base` 와 `typeChecked` 사이에 둔 설정은 덮일 수 있다.
|
|
191
|
+
|
|
192
|
+
## 다른 레포에 적용하기
|
|
193
|
+
|
|
194
|
+
자체 ESLint 설정을 쓰던 레포는 설정을 교체하면서 새로 보고되는 것까지 한 번에 해결한다. 새로 보고되는 것의 대부분은 `eslint --fix` 로 고쳐진다(`array-type`, import 정렬, `import type`, 중복 import 등).
|
|
195
|
+
|
|
196
|
+
1. 설정을 이 패키지의 레이어로 교체하고 규칙 이름을 옮긴다.
|
|
197
|
+
2. `eslint --fix` 를 돌린다.
|
|
198
|
+
3. 남은 것을 직접 고친다.
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
# 2.0.0 이상. eslint 는 9 또는 10
|
|
202
|
+
pnpm add -D @3o3/eslint-config@^2.0.0
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
이 과정을 코딩 에이전트에게 그대로 넘길 프롬프트가 [ADOPTION.md](./ADOPTION.md)에 있다(설치한 뒤에는 `node_modules/@3o3/eslint-config/ADOPTION.md`). 프롬프트는 동작이 달라질 수 있는 수정은 하지 않고 목록으로 보고하게 돼 있다.
|
|
206
|
+
|
|
207
|
+
레포가 쓰던 lint 기준(`--max-warnings`, 경고로 낮추는 설정 등)은 그대로 둔다. 하우스 스타일 규칙까지 한 번에 받기 어려우면 `base` 대신 `core` 로 시작하고, 정리되면 `house` 를 얹는다.
|
|
208
|
+
|
|
80
209
|
## docs 레이어 — 공개 API 문서 강제 (글롭 + 지시어 opt-out)
|
|
81
210
|
|
|
82
211
|
`docs()` 는 `src/**` 의 ts/tsx 에서 **export 된 함수·상수·인터페이스에 문서 블록을
|
|
@@ -104,8 +233,8 @@ export default [
|
|
|
104
233
|
|
|
105
234
|
## 설계 메모
|
|
106
235
|
|
|
107
|
-
-
|
|
108
|
-
`parserOptions.project` 가 필요 없어 어떤 레포에서도 그대로 동작한다.
|
|
236
|
+
- **`base` 는 타입 인지 규칙을 켜지 않는다**: `recommended`(not type-checked)만 켠다 →
|
|
237
|
+
`parserOptions.project` 가 필요 없어 어떤 레포에서도 그대로 동작한다. 타입 인지 규칙은 `typeChecked` 로 얹는다.
|
|
109
238
|
- **무시·프로젝트 규칙은 소비처 config 에서**: base/react 는 순수 룰 레이어라 무시
|
|
110
239
|
규칙을 갖지 않는다. 전역 무시(dist/esm 등)와 프로젝트 고유 규칙은 소비하는 쪽
|
|
111
240
|
`eslint.config` 에서 직접 둔다.
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@3o3/eslint-config",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "3o3 공유 ESLint 설정 — 사용처에 따라 레이어(base/react/docs/all)를 골라 쓴다.",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "3o3 공유 ESLint 설정 — 사용처에 따라 레이어(core/house/base/typeChecked/react/tests/docs/all)를 골라 쓴다.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"main": "./src/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.js",
|
|
10
|
+
"./core": "./src/core.js",
|
|
11
|
+
"./house": "./src/house.js",
|
|
10
12
|
"./base": "./src/base.js",
|
|
13
|
+
"./type-checked": "./src/type-checked.js",
|
|
11
14
|
"./react": "./src/react.js",
|
|
12
15
|
"./docs": "./src/docs.js",
|
|
13
16
|
"./all": "./src/all.js",
|
|
14
17
|
"./package.json": "./package.json"
|
|
15
18
|
},
|
|
16
19
|
"files": [
|
|
17
|
-
"src"
|
|
20
|
+
"src",
|
|
21
|
+
"!src/**/__tests__",
|
|
22
|
+
"ADOPTION.md"
|
|
18
23
|
],
|
|
19
24
|
"repository": {
|
|
20
25
|
"type": "git",
|
|
@@ -23,20 +28,27 @@
|
|
|
23
28
|
},
|
|
24
29
|
"license": "MIT",
|
|
25
30
|
"dependencies": {
|
|
26
|
-
"@
|
|
31
|
+
"@eslint-react/eslint-plugin": "^5.26.1",
|
|
27
32
|
"@eslint/js": "^9.39.4",
|
|
28
33
|
"eslint-config-prettier": "^10.1.8",
|
|
29
34
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
30
|
-
"eslint-plugin-import": "^
|
|
35
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
31
36
|
"eslint-plugin-jsdoc": "^63.0.2",
|
|
32
|
-
"eslint-plugin-react": "^7.37.5",
|
|
33
37
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
38
|
+
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
34
39
|
"eslint-plugin-tsdoc": "^0.5.2",
|
|
35
40
|
"globals": "^15.15.0",
|
|
36
|
-
"typescript-eslint": "^8.
|
|
41
|
+
"typescript-eslint": "^8.71.1"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"eslint": "^10.12.0",
|
|
45
|
+
"eslint9": "npm:eslint@^9.39.4",
|
|
46
|
+
"typescript": "5.9.3",
|
|
47
|
+
"vitest": "4.1.9",
|
|
48
|
+
"@3o3/test-config": "0.1.0"
|
|
37
49
|
},
|
|
38
50
|
"peerDependencies": {
|
|
39
|
-
"eslint": "
|
|
51
|
+
"eslint": "^9.0.0 || ^10.0.0",
|
|
40
52
|
"typescript": ">=5"
|
|
41
53
|
},
|
|
42
54
|
"peerDependenciesMeta": {
|
|
@@ -46,5 +58,8 @@
|
|
|
46
58
|
},
|
|
47
59
|
"publishConfig": {
|
|
48
60
|
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"test": "vitest run"
|
|
49
64
|
}
|
|
50
65
|
}
|
package/src/all.js
CHANGED
|
@@ -11,6 +11,8 @@ import prettier from 'eslint-config-prettier'
|
|
|
11
11
|
*
|
|
12
12
|
* docs 레이어는 `packageDir`/`rootDir` 이 필요한 팩토리라 번들에 포함하지 않는다 —
|
|
13
13
|
* 필요하면 `docs({ packageDir })` 를 직접 스프레드한다.
|
|
14
|
+
* typeChecked 레이어도 포함하지 않는다 — `parserOptions.projectService` 가 필요해 tsconfig 구성이
|
|
15
|
+
* 맞아야 하고 타입 정보 없이 돌 때보다 느리다. 필요하면 all 뒤에 직접 스프레드한다.
|
|
14
16
|
*
|
|
15
17
|
* @example
|
|
16
18
|
* ```js
|
package/src/base.js
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import importPlugin from 'eslint-plugin-import'
|
|
4
|
-
import globals from 'globals'
|
|
1
|
+
import core from './core.js'
|
|
2
|
+
import house from './house.js'
|
|
5
3
|
|
|
6
4
|
/**
|
|
7
|
-
* base 레이어 — js + ts 공통.
|
|
5
|
+
* base 레이어 — js + ts 공통. core(recommended 정합성 규칙) + house(공통 스타일 규칙)를 합친 것이다.
|
|
8
6
|
*
|
|
9
|
-
* 프레임워크에 무관한 3o3 공통 규칙의 단일 출처다.
|
|
10
|
-
*
|
|
11
|
-
* · 3o3 하우스 규칙(naming-convention / member-ordering / eqeqeq / curly 등)
|
|
12
|
-
*
|
|
13
|
-
* 타입 인지(type-aware) 규칙은 켜지 않는다 → `parserOptions.project` 가 필요 없어
|
|
14
|
-
* 어떤 레포에서도 그대로 동작한다(이식성).
|
|
15
|
-
*
|
|
16
|
-
* 모든 `@3o3/eslint-config` 소비의 토대. react 레이어는 이 위에 스프레드한다.
|
|
7
|
+
* 프레임워크에 무관한 3o3 공통 규칙의 단일 출처다. react 레이어는 이 위에 스프레드한다.
|
|
8
|
+
* 하우스 규칙을 나중에 도입하려면 core 만 먼저 쓴다.
|
|
17
9
|
*
|
|
18
10
|
* @example
|
|
19
11
|
* ```js
|
|
@@ -21,95 +13,6 @@ import globals from 'globals'
|
|
|
21
13
|
* export default [...base]
|
|
22
14
|
* ```
|
|
23
15
|
*/
|
|
24
|
-
const base =
|
|
25
|
-
files: ['**/*.{ts,tsx}'],
|
|
26
|
-
extends: [
|
|
27
|
-
js.configs.recommended,
|
|
28
|
-
...tseslint.configs.recommended,
|
|
29
|
-
importPlugin.flatConfigs.recommended,
|
|
30
|
-
importPlugin.flatConfigs.typescript,
|
|
31
|
-
],
|
|
32
|
-
languageOptions: {
|
|
33
|
-
globals: {
|
|
34
|
-
...globals.node,
|
|
35
|
-
...globals.browser,
|
|
36
|
-
...globals.vitest,
|
|
37
|
-
...globals.es2021,
|
|
38
|
-
},
|
|
39
|
-
parserOptions: {
|
|
40
|
-
ecmaFeatures: { jsx: true },
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
settings: {
|
|
44
|
-
'import/resolver': { typescript: {} },
|
|
45
|
-
},
|
|
46
|
-
rules: {
|
|
47
|
-
'no-implicit-coercion': 'error',
|
|
48
|
-
'no-warning-comments': [
|
|
49
|
-
'warn',
|
|
50
|
-
{
|
|
51
|
-
terms: ['TODO', 'FIXME', 'XXX', 'BUG'],
|
|
52
|
-
location: 'anywhere',
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
curly: ['error', 'all'],
|
|
56
|
-
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
|
57
|
-
|
|
58
|
-
// eslint-plugin-import recommended preset에서 신규 적용 — 기존 config는 import 규칙을 켜지 않았으므로 동작 보존 위해 off
|
|
59
|
-
// (import/export는 TS type re-export에 대한 오탐, 실제 중복 export는 tsc typecheck가 잡음)
|
|
60
|
-
'import/export': 'off',
|
|
61
|
-
'import/no-duplicates': 'off',
|
|
62
|
-
|
|
63
|
-
// Hoisting을 전략적으로 사용한 경우가 많아서
|
|
64
|
-
'@typescript-eslint/no-use-before-define': 'off',
|
|
65
|
-
// 모델 정의 부분에서 class와 interface를 합치기 위해 사용하는 용법도 잡고 있어서
|
|
66
|
-
// (v8에서 no-empty-interface는 no-empty-object-type으로 통합됨)
|
|
67
|
-
'@typescript-eslint/no-empty-object-type': 'off',
|
|
68
|
-
// 모델 정의 부분에서 파라미터 프로퍼티를 잘 쓰고 있어서
|
|
69
|
-
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
70
|
-
// (v8에서 no-var-requires는 no-require-imports로 통합됨, 기존 warn 의도 보존)
|
|
71
|
-
'@typescript-eslint/no-require-imports': 'warn',
|
|
72
|
-
'@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
|
|
73
|
-
// v5 recommended에는 있었으나 v8 recommended에서 빠진 규칙. 기존 동작(warn) 보존.
|
|
74
|
-
// (소스에 남아있는 eslint-disable @typescript-eslint/no-non-null-assertion 주석들이 이를 전제로 함)
|
|
75
|
-
'@typescript-eslint/no-non-null-assertion': 'warn',
|
|
76
|
-
'@typescript-eslint/no-inferrable-types': 'warn',
|
|
77
|
-
'@typescript-eslint/no-empty-function': 'off',
|
|
78
|
-
'@typescript-eslint/naming-convention': [
|
|
79
|
-
'error',
|
|
80
|
-
{
|
|
81
|
-
format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
|
|
82
|
-
selector: 'variable',
|
|
83
|
-
leadingUnderscore: 'allow',
|
|
84
|
-
},
|
|
85
|
-
{ format: ['camelCase', 'PascalCase'], selector: 'function' },
|
|
86
|
-
{ format: ['PascalCase'], selector: 'interface' },
|
|
87
|
-
{ format: ['PascalCase'], selector: 'typeAlias' },
|
|
88
|
-
],
|
|
89
|
-
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
90
|
-
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
|
91
|
-
'@typescript-eslint/no-unused-vars': [
|
|
92
|
-
'error',
|
|
93
|
-
// v8에서 caughtErrors 기본값이 'none' → 'all'로 바뀌어 catch (e) 바인딩까지 잡는다.
|
|
94
|
-
// 기존(v5) 동작 보존을 위해 caughtErrors를 'none'으로 복원.
|
|
95
|
-
{ ignoreRestSiblings: true, caughtErrors: 'none' },
|
|
96
|
-
],
|
|
97
|
-
'@typescript-eslint/member-ordering': [
|
|
98
|
-
'error',
|
|
99
|
-
{
|
|
100
|
-
default: [
|
|
101
|
-
'public-static-field',
|
|
102
|
-
'private-static-field',
|
|
103
|
-
'public-instance-field',
|
|
104
|
-
'private-instance-field',
|
|
105
|
-
'public-constructor',
|
|
106
|
-
'private-constructor',
|
|
107
|
-
'public-instance-method',
|
|
108
|
-
'private-instance-method',
|
|
109
|
-
],
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
})
|
|
16
|
+
const base = [...core, ...house]
|
|
114
17
|
|
|
115
18
|
export default base
|
package/src/core.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
import importX from 'eslint-plugin-import-x'
|
|
4
|
+
import globals from 'globals'
|
|
5
|
+
import plugin from './plugin.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* core 레이어 — js + ts 정합성 규칙.
|
|
9
|
+
*
|
|
10
|
+
* 스타일 취향과 무관하게 모든 3o3 TS 코드가 따르는 규칙이다. 각 preset 의 recommended 를 그대로 쓴다.
|
|
11
|
+
* · @eslint/js + typescript-eslint(recommended) + eslint-plugin-import-x
|
|
12
|
+
* · 사내 규칙(@3o3/no-app-event-on-window)
|
|
13
|
+
*
|
|
14
|
+
* 타입 인지(type-aware) 규칙은 켜지 않는다 → `parserOptions.project` 가 필요 없어
|
|
15
|
+
* 어떤 레포에서도 그대로 동작한다(이식성). 타입 인지 규칙은 typeChecked 레이어로 얹는다.
|
|
16
|
+
*
|
|
17
|
+
* 하우스 규칙(house 레이어)을 나중에 도입하려는 레포는 base 대신 이 레이어부터 쓴다.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```js
|
|
21
|
+
* import { core } from '@3o3/eslint-config'
|
|
22
|
+
* export default [...core]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
const core = tseslint.config({
|
|
26
|
+
files: ['**/*.{ts,tsx,mts,cts}'],
|
|
27
|
+
extends: [
|
|
28
|
+
js.configs.recommended,
|
|
29
|
+
...tseslint.configs.recommended,
|
|
30
|
+
// eslint-plugin-import 의 typescript preset 처럼 node resolver 를 typescript resolver 보다 먼저 둔다 (settings 는 뒤 항목과 합쳐진다).
|
|
31
|
+
// 예전 node resolver 는 package.json 의 exports 를 읽지 않았으므로 그대로 맞춘다.
|
|
32
|
+
// (typescript resolver 만 쓰면 패키지가 .d.ts 로 풀려 no-named-as-default-member 등의 결과가 달라진다)
|
|
33
|
+
{
|
|
34
|
+
settings: {
|
|
35
|
+
'import-x/resolver': {
|
|
36
|
+
node: {
|
|
37
|
+
// import-x 의 typescript preset 이 쓰는 확장자 목록을 그대로 따라간다 (import-x 를 올리면 함께 바뀐다)
|
|
38
|
+
extensions:
|
|
39
|
+
importX.flatConfigs.typescript.settings['import-x/extensions'],
|
|
40
|
+
exportsFields: [],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
importX.flatConfigs.recommended,
|
|
46
|
+
importX.flatConfigs.typescript,
|
|
47
|
+
],
|
|
48
|
+
languageOptions: {
|
|
49
|
+
// typescript-eslint recommended 가 TS 파일의 no-undef 를 끄므로 이 globals 를 쓰는 규칙은 없다 (이전 설정에서 그대로 옮긴 것)
|
|
50
|
+
globals: {
|
|
51
|
+
...globals.node,
|
|
52
|
+
...globals.browser,
|
|
53
|
+
...globals.vitest,
|
|
54
|
+
...globals.es2021,
|
|
55
|
+
},
|
|
56
|
+
parserOptions: {
|
|
57
|
+
ecmaFeatures: { jsx: true },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
plugins: { '@3o3': plugin },
|
|
61
|
+
rules: {
|
|
62
|
+
// window 에 직접 붙인 등록형 앱 이벤트는 앱에 등록되지 않는다
|
|
63
|
+
'@3o3/no-app-event-on-window': 'error',
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export default core
|
package/src/house.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import simpleImportSort from 'eslint-plugin-simple-import-sort'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* house 레이어 — 3o3 공통 스타일 규칙.
|
|
6
|
+
*
|
|
7
|
+
* recommended 에 더해 서비스·라이브러리 레포가 함께 지키는 스타일 규칙이다. 대부분 `eslint --fix` 로 고쳐진다.
|
|
8
|
+
* core 레이어 위에 스프레드한다(`@typescript-eslint` 플러그인과 파서를 core 가 등록한다).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```js
|
|
12
|
+
* import { core, house } from '@3o3/eslint-config'
|
|
13
|
+
* export default [...core, ...house]
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
const house = tseslint.config({
|
|
17
|
+
files: ['**/*.{ts,tsx,mts,cts}'],
|
|
18
|
+
plugins: { 'simple-import-sort': simpleImportSort },
|
|
19
|
+
rules: {
|
|
20
|
+
'no-implicit-coercion': 'error',
|
|
21
|
+
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
|
22
|
+
'@typescript-eslint/no-inferrable-types': 'warn',
|
|
23
|
+
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
|
24
|
+
// 타입만 가져올 때는 `import type` 을 쓴다
|
|
25
|
+
'@typescript-eslint/consistent-type-imports': 'error',
|
|
26
|
+
'simple-import-sort/imports': 'error',
|
|
27
|
+
'simple-import-sort/exports': 'error',
|
|
28
|
+
// rest 로 나머지를 받으려고 꺼낸 프로퍼티(`const { a, ...rest } = x`)와 `_` 로 시작하는 인자는 허용한다
|
|
29
|
+
'@typescript-eslint/no-unused-vars': [
|
|
30
|
+
'error',
|
|
31
|
+
{ ignoreRestSiblings: true, argsIgnorePattern: '^_' },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export default house
|
package/src/index.js
CHANGED
|
@@ -1,51 +1,31 @@
|
|
|
1
|
+
import core from './core.js'
|
|
2
|
+
import house from './house.js'
|
|
1
3
|
import base from './base.js'
|
|
4
|
+
import typeChecked from './type-checked.js'
|
|
2
5
|
import reactLayer from './react.js'
|
|
3
6
|
import testsLayer from './tests.js'
|
|
4
7
|
import all from './all.js'
|
|
5
8
|
import { docs } from './docs.js'
|
|
6
9
|
import eslintConfigPrettier from 'eslint-config-prettier'
|
|
10
|
+
import plugin from './plugin.js'
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* ```js
|
|
21
|
-
* import { defineConfig } from 'eslint/config'
|
|
22
|
-
* import threeo3 from '@3o3/eslint-config'
|
|
23
|
-
* export default defineConfig([
|
|
24
|
-
* { plugins: { '@3o3': threeo3 }, extends: ['@3o3/base', '@3o3/react'] },
|
|
25
|
-
* ])
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* default export 는 `configs` 맵을 가진 플러그인 형태(typescript-eslint /
|
|
29
|
-
* eslint-plugin-react 와 동일한 패턴)라 `extends: ['@3o3/<layer>']` 문자열로
|
|
30
|
-
* 참조된다. `<layer>` 는 base / react / all.
|
|
31
|
-
*/
|
|
32
|
-
const plugin = {
|
|
33
|
-
meta: { name: '@3o3/eslint-config' },
|
|
34
|
-
// `extends: ['@3o3/<layer>']` 문자열 참조용(typescript-eslint / react 패턴).
|
|
35
|
-
configs: {
|
|
36
|
-
base,
|
|
37
|
-
react: reactLayer,
|
|
38
|
-
tests: testsLayer,
|
|
39
|
-
all,
|
|
40
|
-
},
|
|
41
|
-
docs,
|
|
42
|
-
prettier: eslintConfigPrettier,
|
|
43
|
-
}
|
|
12
|
+
// core 가 plugin 을 import 하고 plugin.configs 에는 core 가 담기므로, 순환 import 가 되지 않게 레이어는 여기서 채운다.
|
|
13
|
+
// docs·prettier 도 여기서 붙여, base 만 가져오는 소비처가 문서용 플러그인까지 불러오지 않게 한다
|
|
14
|
+
Object.assign(plugin.configs, {
|
|
15
|
+
core,
|
|
16
|
+
house,
|
|
17
|
+
base,
|
|
18
|
+
typeChecked,
|
|
19
|
+
react: reactLayer,
|
|
20
|
+
tests: testsLayer,
|
|
21
|
+
all,
|
|
22
|
+
})
|
|
23
|
+
Object.assign(plugin, { docs, prettier: eslintConfigPrettier })
|
|
44
24
|
|
|
45
25
|
export default plugin
|
|
46
26
|
|
|
47
27
|
// 배열 스프레드용 named export (기존 방식 유지)
|
|
48
|
-
export { base, all }
|
|
28
|
+
export { core, house, base, typeChecked, all }
|
|
49
29
|
export { reactLayer as react }
|
|
50
30
|
export { testsLayer as tests }
|
|
51
31
|
export { docs }
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import noAppEventOnWindow from './rules/no-app-event-on-window.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `@3o3/eslint-config` — 사용처별 레이어를 골라 쓰는 공유 ESLint 설정(flat config).
|
|
5
|
+
*
|
|
6
|
+
* 두 가지 소비 방식을 모두 지원한다.
|
|
7
|
+
*
|
|
8
|
+
* 1) 배열 스프레드(named export):
|
|
9
|
+
* ```js
|
|
10
|
+
* import { base, react } from '@3o3/eslint-config'
|
|
11
|
+
* export default [...base, ...react]
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* 2) ESLint 가 권장하는 `extends` 스타일(default export + `defineConfig`):
|
|
15
|
+
* ```js
|
|
16
|
+
* import { defineConfig } from 'eslint/config'
|
|
17
|
+
* import threeo3 from '@3o3/eslint-config'
|
|
18
|
+
* export default defineConfig([
|
|
19
|
+
* { plugins: { '@3o3': threeo3 }, extends: ['@3o3/base', '@3o3/react'] },
|
|
20
|
+
* ])
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* default export 는 `configs` 맵을 가진 플러그인 형태(typescript-eslint 와 동일한 패턴)라
|
|
24
|
+
* `extends: ['@3o3/<layer>']` 문자열로 참조된다. `<layer>` 는 core / house / base / typeChecked / react / tests / all.
|
|
25
|
+
*
|
|
26
|
+
* core 가 이 객체를 `@3o3` 로 등록한다. 소비처가 다시 등록해도 같은 객체라 중복 정의가 아니다.
|
|
27
|
+
*/
|
|
28
|
+
const plugin = {
|
|
29
|
+
meta: { name: '@3o3/eslint-config' },
|
|
30
|
+
rules: {
|
|
31
|
+
'no-app-event-on-window': noAppEventOnWindow,
|
|
32
|
+
},
|
|
33
|
+
configs: {},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default plugin
|
package/src/react.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import tseslint from 'typescript-eslint'
|
|
2
|
-
import
|
|
2
|
+
import eslintReact from '@eslint-react/eslint-plugin'
|
|
3
3
|
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
-
import * as emotionPlugin from '@emotion/eslint-plugin'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* react 레이어 — base 위에 얹는 React 델타.
|
|
8
7
|
*
|
|
9
|
-
* eslint-
|
|
10
|
-
*
|
|
8
|
+
* @eslint-react(React 규칙) + react-hooks(rules-of-hooks / exhaustive-deps) 를 포함한다.
|
|
9
|
+
* base 를 먼저 스프레드한 뒤 이 레이어를 스프레드한다.
|
|
11
10
|
*
|
|
12
|
-
* react
|
|
13
|
-
*
|
|
11
|
+
* eslint-plugin-react 는 ESLint 10 을 지원하지 않아 @eslint-react 로 바꿨다. @eslint-react 의
|
|
12
|
+
* recommended preset 은 60개가 넘는 규칙을 켜서 기존 동작과 크게 달라지므로,
|
|
13
|
+
* preset 을 쓰지 않고 eslint-plugin-react recommended 에서 켜져 있던 규칙에 대응하는 것만 켠다.
|
|
14
|
+
* 훅 규칙은 기존 `react-hooks/*` 이름을 유지하려고 eslint-plugin-react-hooks 를 그대로 쓴다.
|
|
15
|
+
*
|
|
16
|
+
* react-hooks v7 의 preset 은 React Compiler 규칙까지 켜는 슈퍼셋이라
|
|
17
|
+
* 기존 동작과 크게 달라진다. 따라서 preset을 쓰지 않고
|
|
14
18
|
* 플러그인만 등록한 뒤 기존에 켜져 있던 두 규칙만 명시적으로 활성화한다.
|
|
15
19
|
*
|
|
16
20
|
* @example
|
|
@@ -20,32 +24,41 @@ import * as emotionPlugin from '@emotion/eslint-plugin'
|
|
|
20
24
|
* ```
|
|
21
25
|
*/
|
|
22
26
|
const reactLayer = tseslint.config({
|
|
23
|
-
files: ['**/*.{ts,tsx}'],
|
|
24
|
-
extends: [react.configs.flat.recommended],
|
|
25
|
-
languageOptions: {
|
|
26
|
-
parserOptions: {
|
|
27
|
-
ecmaFeatures: { jsx: true },
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
settings: {
|
|
31
|
-
react: { version: 'detect' },
|
|
32
|
-
},
|
|
27
|
+
files: ['**/*.{ts,tsx,mts,cts}'],
|
|
33
28
|
plugins: {
|
|
34
|
-
// @
|
|
35
|
-
'@
|
|
29
|
+
// dom-* 규칙도 이 플러그인의 rules 에 함께 들어 있어 '@eslint-react/dom' 을 따로 등록하지 않는다
|
|
30
|
+
'@eslint-react': eslintReact,
|
|
36
31
|
'react-hooks': reactHooks,
|
|
37
32
|
},
|
|
38
33
|
rules: {
|
|
39
|
-
'@emotion/pkg-renaming': 'error',
|
|
40
|
-
|
|
41
|
-
'react/prop-types': 'off',
|
|
42
|
-
// React.memo, React.forwardRef에서 사용하는 경우도 막고 있어서
|
|
43
|
-
'react/display-name': 'off',
|
|
44
|
-
// 기존 v4 recommended preset에서 켜져 있던 규칙 (수동 등록 시 명시 필요)
|
|
45
34
|
'react-hooks/rules-of-hooks': 'error',
|
|
46
35
|
'react-hooks/exhaustive-deps': 'error',
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
|
|
37
|
+
// 주석은 예전 규칙 이름. 이 대응을 바꾸면 README.md·ADOPTION.md 의 대응표도 고친다
|
|
38
|
+
// react/jsx-key
|
|
39
|
+
'@eslint-react/no-missing-key': 'error',
|
|
40
|
+
// react/jsx-no-comment-textnodes
|
|
41
|
+
'@eslint-react/jsx-no-comment-textnodes': 'error',
|
|
42
|
+
// react/jsx-no-target-blank
|
|
43
|
+
'@eslint-react/dom-no-unsafe-target-blank': 'error',
|
|
44
|
+
// react/no-children-prop
|
|
45
|
+
'@eslint-react/jsx-no-children-prop': 'error',
|
|
46
|
+
// react/no-danger-with-children
|
|
47
|
+
'@eslint-react/dom-no-dangerously-set-innerhtml-with-children': 'error',
|
|
48
|
+
// react/no-direct-mutation-state
|
|
49
|
+
'@eslint-react/no-direct-mutation-state': 'error',
|
|
50
|
+
// react/no-find-dom-node
|
|
51
|
+
'@eslint-react/dom-no-find-dom-node': 'error',
|
|
52
|
+
// react/no-render-return-value
|
|
53
|
+
'@eslint-react/dom-no-render-return-value': 'error',
|
|
54
|
+
// react/no-unknown-property
|
|
55
|
+
'@eslint-react/dom-no-unknown-property': ['error', { ignore: ['css'] }],
|
|
56
|
+
// react/no-deprecated
|
|
57
|
+
'@eslint-react/no-component-will-mount': 'error',
|
|
58
|
+
'@eslint-react/no-component-will-receive-props': 'error',
|
|
59
|
+
'@eslint-react/no-component-will-update': 'error',
|
|
60
|
+
'@eslint-react/dom-no-render': 'error',
|
|
61
|
+
'@eslint-react/dom-no-hydrate': 'error',
|
|
49
62
|
},
|
|
50
63
|
})
|
|
51
64
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const APP_EVENT_PREFIX = 'flutter_'
|
|
2
|
+
const WINDOW_NAMES = ['window', 'globalThis', 'self']
|
|
3
|
+
const METHODS = ['addEventListener', 'removeEventListener']
|
|
4
|
+
const APP_BRIDGE = '@3o3/app-bridge'
|
|
5
|
+
|
|
6
|
+
const findVariable = (scope, name) => {
|
|
7
|
+
for (let current = scope; current; current = current.upper) {
|
|
8
|
+
const variable = current.set.get(name)
|
|
9
|
+
if (variable) {
|
|
10
|
+
return variable
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 앱 이벤트(flutter_*)를 window 에 직접 붙이는 코드를 막는다. 이벤트 이름이 문자열 리터럴인 호출만 잡는다.
|
|
18
|
+
* 등록형 이벤트(onBackPressed)는 window 에 직접 붙이면 앱에 등록되지 않는다.
|
|
19
|
+
*/
|
|
20
|
+
const rule = {
|
|
21
|
+
meta: {
|
|
22
|
+
type: 'problem',
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
docs: {
|
|
25
|
+
description:
|
|
26
|
+
'앱 이벤트(flutter_*)는 window 대신 @3o3/app-bridge 의 webView 로 구독한다',
|
|
27
|
+
},
|
|
28
|
+
schema: [],
|
|
29
|
+
messages: {
|
|
30
|
+
useWebView:
|
|
31
|
+
"앱 이벤트 '{{event}}' 는 window 에 직접 붙이지 말고 @3o3/app-bridge 의 webView.{{method}} 를 쓰세요. 등록형 이벤트는 직접 붙이면 앱에 등록되지 않습니다.",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
create(context) {
|
|
35
|
+
const { sourceCode } = context
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
CallExpression(node) {
|
|
39
|
+
const { callee } = node
|
|
40
|
+
const [eventName] = node.arguments
|
|
41
|
+
if (
|
|
42
|
+
callee.type !== 'MemberExpression' ||
|
|
43
|
+
callee.computed ||
|
|
44
|
+
callee.object.type !== 'Identifier' ||
|
|
45
|
+
!WINDOW_NAMES.includes(callee.object.name) ||
|
|
46
|
+
callee.property.type !== 'Identifier' ||
|
|
47
|
+
!METHODS.includes(callee.property.name) ||
|
|
48
|
+
eventName?.type !== 'Literal' ||
|
|
49
|
+
typeof eventName.value !== 'string' ||
|
|
50
|
+
!eventName.value.startsWith(APP_EVENT_PREFIX)
|
|
51
|
+
) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const program = sourceCode.ast
|
|
56
|
+
const appBridgeImports = program.body.filter(
|
|
57
|
+
(statement) =>
|
|
58
|
+
statement.type === 'ImportDeclaration' &&
|
|
59
|
+
statement.source.value === APP_BRIDGE &&
|
|
60
|
+
statement.importKind !== 'type'
|
|
61
|
+
)
|
|
62
|
+
const webViewSpecifier = appBridgeImports
|
|
63
|
+
.flatMap((declaration) => declaration.specifiers)
|
|
64
|
+
.find(
|
|
65
|
+
(specifier) =>
|
|
66
|
+
specifier.type === 'ImportSpecifier' &&
|
|
67
|
+
specifier.imported.name === 'webView' &&
|
|
68
|
+
specifier.importKind !== 'type'
|
|
69
|
+
)
|
|
70
|
+
const localName = webViewSpecifier?.local.name ?? 'webView'
|
|
71
|
+
|
|
72
|
+
// webView 가 다른 값을 가리키면 고치지 않는다
|
|
73
|
+
const variable = findVariable(sourceCode.getScope(node), localName)
|
|
74
|
+
const canFix = webViewSpecifier
|
|
75
|
+
? variable?.defs[0]?.node === webViewSpecifier
|
|
76
|
+
: !variable
|
|
77
|
+
|
|
78
|
+
const importFixes = (fixer) => {
|
|
79
|
+
if (webViewSpecifier) {
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const namedImport = appBridgeImports.find((declaration) =>
|
|
84
|
+
declaration.specifiers.some(
|
|
85
|
+
(specifier) => specifier.type === 'ImportSpecifier'
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
if (namedImport) {
|
|
89
|
+
return [
|
|
90
|
+
fixer.insertTextAfter(namedImport.specifiers.at(-1), ', webView'),
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 'use client' 같은 지시어는 맨 위에 남아야 한다
|
|
95
|
+
const anchor =
|
|
96
|
+
program.body.find(
|
|
97
|
+
(statement) => statement.type === 'ImportDeclaration'
|
|
98
|
+
) ?? program.body.find((statement) => !statement.directive)
|
|
99
|
+
return [
|
|
100
|
+
fixer.insertTextBefore(
|
|
101
|
+
anchor,
|
|
102
|
+
`import { webView } from '${APP_BRIDGE}'\n`
|
|
103
|
+
),
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
context.report({
|
|
108
|
+
node,
|
|
109
|
+
messageId: 'useWebView',
|
|
110
|
+
data: { event: eventName.value, method: callee.property.name },
|
|
111
|
+
fix: canFix
|
|
112
|
+
? (fixer) => [
|
|
113
|
+
fixer.replaceText(callee.object, localName),
|
|
114
|
+
...importFixes(fixer),
|
|
115
|
+
]
|
|
116
|
+
: null,
|
|
117
|
+
})
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default rule
|
package/src/tests.js
CHANGED
|
@@ -3,12 +3,13 @@ import tseslint from 'typescript-eslint'
|
|
|
3
3
|
/**
|
|
4
4
|
* tests 레이어 — 테스트 파일에만 적용하는 완화 규칙.
|
|
5
5
|
*
|
|
6
|
-
* 대상: `**\/__tests__/**\/*.{ts,tsx}` 와 `**\/*.spec.{ts,tsx}
|
|
6
|
+
* 대상: `**\/__tests__/**\/*.{ts,tsx,mts,cts}` 와 `**\/*.{spec,test}.{ts,tsx,mts,cts}`.
|
|
7
7
|
*
|
|
8
8
|
* 테스트는 목·스텁·DOM 노드 접근 때문에 non-null 어설션(`!`)을 자연스럽게 쓰고,
|
|
9
|
-
* 임시 픽스처 배열 타입도 `T[]` 로 두는 편이 읽기 쉽다.
|
|
10
|
-
* `no-non-null-assertion`
|
|
11
|
-
*
|
|
9
|
+
* 임시 픽스처 배열 타입도 `T[]` 로 두는 편이 읽기 쉽다. 그래서 테스트 파일에서는
|
|
10
|
+
* `array-type`(house)과 `no-non-null-assertion` 을 off 한다. `no-non-null-assertion` 은
|
|
11
|
+
* 이 패키지가 켜지 않지만, 레포에서 직접 켠 경우에도 테스트에서는 꺼지도록 둔다.
|
|
12
|
+
* base/react 레이어와 레포 고유 규칙 뒤에 스프레드해야 오버라이드가 적용된다.
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```js
|
|
@@ -20,7 +21,10 @@ import tseslint from 'typescript-eslint'
|
|
|
20
21
|
* ```
|
|
21
22
|
*/
|
|
22
23
|
const tests = tseslint.config({
|
|
23
|
-
files: [
|
|
24
|
+
files: [
|
|
25
|
+
'**/__tests__/**/*.{ts,tsx,mts,cts}',
|
|
26
|
+
'**/*.{spec,test}.{ts,tsx,mts,cts}',
|
|
27
|
+
],
|
|
24
28
|
rules: {
|
|
25
29
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
26
30
|
'@typescript-eslint/array-type': 'off',
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import tseslint from 'typescript-eslint'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* typeChecked 레이어 — 타입 정보를 쓰는 규칙(typescript-eslint recommended-type-checked 의 타입 인지 규칙).
|
|
5
|
+
*
|
|
6
|
+
* `no-floating-promises`, `no-misused-promises`, `no-unsafe-*` 등을 켠다. 타입 정보를 얻기 위해
|
|
7
|
+
* `parserOptions.projectService` 를 켜므로, lint 대상 파일이 tsconfig 에 포함돼 있어야 하고
|
|
8
|
+
* 타입 정보 없이 돌 때보다 느리다. 그래서 base 에 넣지 않고 따로 얹는다. core(또는 base) 위에 스프레드한다.
|
|
9
|
+
* 켜지는 규칙은 typescript-eslint 의 preset 을 따르므로 typescript-eslint 를 올리면 달라질 수 있다.
|
|
10
|
+
* tsconfig 가 ESLint 를 실행하는 디렉터리보다 위에 있으면 `parserOptions.tsconfigRootDir` 를 함께 지정한다(README 참고).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```js
|
|
14
|
+
* import { base, typeChecked } from '@3o3/eslint-config'
|
|
15
|
+
* export default [...base, ...typeChecked]
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const typeChecked = tseslint.config({
|
|
19
|
+
files: ['**/*.{ts,tsx,mts,cts}'],
|
|
20
|
+
extends: [tseslint.configs.recommendedTypeCheckedOnly],
|
|
21
|
+
languageOptions: {
|
|
22
|
+
parserOptions: { projectService: true },
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export default typeChecked
|