eslint-plugin-aria-state-validator 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 comento
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.ko.md ADDED
@@ -0,0 +1,382 @@
1
+ # eslint-plugin-aria-state-validator
2
+
3
+ ARIA 접근성을 위한 포괄적인 ESLint 플러그인:
4
+
5
+ - **동적 상태 검증**: ARIA 상태 속성이 컴포넌트 상태와 올바르게 연결되었는지 검증
6
+ - **정적 ARIA 검증**: 정적 ARIA 속성의 정확성 검증 (철자, 값, 충돌)
7
+
8
+ 빌드 타임에 런타임 및 정적 접근성 오류를 미리 감지합니다.
9
+
10
+ ## 문제 제기: 정적 분석의 '런타임 공백'
11
+
12
+ `eslint-plugin-jsx-a11y` 같은 기존 접근성 도구들은 ARIA 속성의 문법적 정확성(예: `aria-expanded`가 `'true'` 또는 `'false'`를 사용하는지)은 검증하지만, 이 속성들이 컴포넌트의 JavaScript 상태와 올바르게 연결되어 런타임에 동적으로 업데이트되는지는 **검증할 수 없습니다**.
13
+
14
+ ### 발생하는 문제:
15
+
16
+ - 토글 버튼의 `aria-expanded="false"`가 하드코딩되어 열려도 변경되지 않음
17
+ - ARIA 속성에 Boolean 값을 직접 바인딩: `aria-expanded={isOpen}` (오류 - 문자열이어야 함)
18
+ - 인터랙티브 요소의 정적 ARIA 상태
19
+ - 스크린 리더 사용자에게 부정확한 정보 전달
20
+
21
+ ## 핵심 컨셉: 상태 의존 ARIA 검증
22
+
23
+ 이 플러그인은 AST(추상 구문 트리) 분석을 통해 ARIA 상태 속성이 컴포넌트 상태(State/Props)와 논리적으로 연결되어 런타임에 업데이트될 것인지 **정적으로 검증**합니다.
24
+
25
+ ### 차별점
26
+
27
+ 단순 문법 검사를 넘어 컴포넌트의 **동적 동작**을 추론하여, 런타임에서만 발견되던 접근성 오류를 빌드/CI 단계에서 선제적으로 포착합니다.
28
+
29
+ ## 설치
30
+
31
+ ```bash
32
+ npm install --save-dev eslint-plugin-aria-state-validator
33
+ ```
34
+
35
+ ## 사용법
36
+
37
+ ### ESLint Flat Config (ESLint 9+)
38
+
39
+ ```javascript
40
+ // eslint.config.js
41
+ import ariaStateValidator from "eslint-plugin-aria-state-validator";
42
+
43
+ export default [
44
+ {
45
+ plugins: {
46
+ "aria-state-validator": ariaStateValidator,
47
+ },
48
+ rules: {
49
+ "aria-state-validator/state-dependent-aria-validator": "warn",
50
+ "aria-state-validator/static-aria-validator": "warn",
51
+ },
52
+ },
53
+ ];
54
+ ```
55
+
56
+ ### Legacy ESLintRC Config
57
+
58
+ ```json
59
+ {
60
+ "plugins": ["aria-state-validator"],
61
+ "rules": {
62
+ "aria-state-validator/state-dependent-aria-validator": "warn",
63
+ "aria-state-validator/static-aria-validator": "warn"
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 설정 프리셋
69
+
70
+ ```javascript
71
+ // 권장 설정 (경고)
72
+ ...ariaStateValidator.configs.recommended
73
+
74
+ // 엄격한 설정 (에러)
75
+ ...ariaStateValidator.configs.strict
76
+
77
+ // 동적 검증만
78
+ ...ariaStateValidator.configs['dynamic-only']
79
+
80
+ // 정적 검증만
81
+ ...ariaStateValidator.configs['static-only']
82
+ ```
83
+
84
+ ## 두 가지 규칙으로 포괄적인 ARIA 검증
85
+
86
+ 이 플러그인은 두 가지 보완적인 규칙을 제공합니다:
87
+
88
+ ### 규칙 1: `state-dependent-aria-validator` (동적 상태 검증)
89
+
90
+ ARIA **상태** 속성이 컴포넌트 상태와 올바르게 연결되어 동적으로 업데이트되는지 검증합니다.
91
+
92
+ ### 규칙 2: `static-aria-validator` (정적 ARIA 검증)
93
+
94
+ 사용된 ARIA 속성의 **정확성**을 검증합니다:
95
+
96
+ - 유효한 ARIA 속성 이름 (오타 감지)
97
+ - Boolean 및 Enum ARIA 속성의 올바른 값
98
+ - 충돌하는 ARIA 속성 감지
99
+ - 네이티브 HTML 요소의 중복 role 감지
100
+
101
+ **참고:** 이 플러그인은 ARIA 속성을 **어떻게** 사용하는지를 검증하며, **사용 여부**를 강제하지는 않습니다. 접근성 기능의 존재를 강제하려면 [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y)를 사용하세요.
102
+
103
+ ## 기능 및 검증 패턴
104
+
105
+ ## 동적 상태 검증 패턴
106
+
107
+ ### 1. Boolean 값 직접 바인딩 감지
108
+
109
+ **감지 내용:** ARIA 속성에 Boolean 값을 직접 바인딩
110
+
111
+ ```jsx
112
+ // ❌ 오류: Boolean 값 직접 바인딩
113
+ <button aria-expanded={isOpen}>토글</button>
114
+
115
+ // ✅ 올바름: 명시적 문자열 변환
116
+ <button aria-expanded={isOpen ? 'true' : 'false'}>토글</button>
117
+ ```
118
+
119
+ **자동 수정 가능:** `eslint --fix`가 자동으로 올바른 문자열 형식으로 변환합니다
120
+
121
+ ### 2. 정적 값 + 인터랙티브 핸들러 감지
122
+
123
+ **감지 내용:** 이벤트 핸들러가 있는 요소의 정적 ARIA 상태 값
124
+
125
+ ```jsx
126
+ // ❌ 오류: onClick이 있지만 aria-expanded가 정적
127
+ <button onClick={handleClick} aria-expanded="false">
128
+ 토글
129
+ </button>
130
+
131
+ // ✅ 올바름: 동적 상태 바인딩
132
+ <button onClick={handleClick} aria-expanded={isOpen ? 'true' : 'false'}>
133
+ 토글
134
+ </button>
135
+ ```
136
+
137
+ **스마트 자동 수정:** 핸들러에 상태 변수 참조가 있으면 (예: `() => setExpanded(!expanded)`), 플러그인이 자동으로 변수 이름을 추출하여 ARIA 속성에 바인딩합니다:
138
+
139
+ ```jsx
140
+ // ❌ 자동 수정 전
141
+ <div onClick={() => setExpanded(!expanded)} aria-expanded="false">
142
+ 클릭하여 확장
143
+ </div>
144
+
145
+ // ✅ 자동 수정 후 (자동으로 'expanded' 변수 감지)
146
+ <div onClick={() => setExpanded(!expanded)} aria-expanded={expanded ? 'true' : 'false'}>
147
+ 클릭하여 확장
148
+ </div>
149
+ ```
150
+
151
+ ### 3. 역할 기반 상태 연관성 검증
152
+
153
+ **감지 내용:** 정적 ARIA 상태를 가진 인터랙티브 역할
154
+
155
+ ```jsx
156
+ // ❌ 오류: role="button"인데 aria-pressed가 정적
157
+ <div role="button" aria-pressed="false">버튼</div>
158
+
159
+ // ✅ 올바름: 동적 상태 바인딩
160
+ <div role="button" aria-pressed={isPressed ? 'true' : 'false'}>버튼</div>
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 정적 ARIA 검증 패턴
166
+
167
+ ### 4. 유효하지 않은 ARIA 속성 감지
168
+
169
+ **감지 내용:** ARIA 속성 이름의 오타
170
+
171
+ ```jsx
172
+ // ❌ 오류: aria-label의 오타
173
+ <button aria-labell="닫기">X</button>
174
+
175
+ // ✅ 올바름: 올바른 철자
176
+ <button aria-label="닫기">X</button>
177
+ ```
178
+
179
+ ### 5. 빈 필수 값
180
+
181
+ **감지 내용:** 비어있지 않은 값이 필요한 ARIA 속성
182
+
183
+ ```jsx
184
+ // ❌ 오류: aria-label이 비어있음
185
+ <div aria-label="">내용</div>
186
+
187
+ // ✅ 올바름: 비어있지 않은 값
188
+ <div aria-label="설명">내용</div>
189
+ ```
190
+
191
+ ### 6. 유효하지 않은 Boolean 값
192
+
193
+ **감지 내용:** 잘못된 boolean 형식 값
194
+
195
+ ```jsx
196
+ // ❌ 오류: 유효하지 않은 boolean 값
197
+ <div aria-hidden="yes">내용</div>
198
+ <div aria-disabled="1">내용</div>
199
+
200
+ // ✅ 올바름: "true" 또는 "false" 사용
201
+ <div aria-hidden="true">내용</div>
202
+ <div aria-disabled="false">내용</div>
203
+ ```
204
+
205
+ ### 7. 유효하지 않은 Enum 값
206
+
207
+ **감지 내용:** Enum 타입 ARIA 속성의 유효하지 않은 값
208
+
209
+ ```jsx
210
+ // ❌ 오류: 유효하지 않은 aria-live 값
211
+ <div aria-live="loud">라이브 영역</div>
212
+
213
+ // ✅ 올바름: 유효한 enum 값 사용
214
+ <div aria-live="polite">라이브 영역</div>
215
+
216
+ // 유효한 값: "off", "polite", "assertive"
217
+ ```
218
+
219
+ ### 8. 충돌하는 ARIA 속성
220
+
221
+ **감지 내용:** aria-label과 aria-labelledby를 함께 사용
222
+
223
+ ```jsx
224
+ // ❌ 오류: 두 속성 모두 존재 (aria-labelledby가 우선)
225
+ <div aria-label="레이블" aria-labelledby="other-id">내용</div>
226
+
227
+ // ✅ 올바름: 하나만 사용
228
+ <div aria-labelledby="other-id">내용</div>
229
+ ```
230
+
231
+ ### 9. 중복 Role
232
+
233
+ **감지 내용:** 네이티브 HTML 시맨틱과 일치하는 명시적 role
234
+
235
+ ```jsx
236
+ // ❌ 오류: 중복 role
237
+ <button role="button">클릭</button>
238
+ <nav role="navigation">내비게이션</nav>
239
+ <a href="#" role="link">링크</a>
240
+
241
+ // ✅ 올바름: 네이티브 시맨틱 사용
242
+ <button>클릭</button>
243
+ <nav>내비게이션</nav>
244
+ <a href="#">링크</a>
245
+ ```
246
+
247
+ ---
248
+
249
+ ## 지원하는 ARIA 속성 (동적 검증)
250
+
251
+ 플러그인이 검증하는 동적 ARIA 상태 속성:
252
+
253
+ - `aria-expanded` - 토글 버튼, 확장 가능한 요소
254
+ - `aria-selected` - 탭, 선택 가능한 옵션
255
+ - `aria-checked` - 체크박스, 라디오 버튼
256
+ - `aria-pressed` - 토글 버튼
257
+ - `aria-hidden` - 동적으로 표시/숨김되는 요소
258
+ - `aria-disabled` - 동적으로 활성화/비활성화되는 요소
259
+ - `aria-modal` - 다이얼로그, 모달
260
+ - `aria-current` - 현재 활성 항목 표시
261
+
262
+ ## 지원하는 인터랙티브 역할
263
+
264
+ 다음 역할을 가진 요소는 동적 ARIA 상태가 필요합니다:
265
+
266
+ - `button`, `tab`, `checkbox`, `radio`, `switch`
267
+ - `menuitem`, `menuitemcheckbox`, `menuitemradio`
268
+ - `option`, `treeitem`
269
+ - `dialog`, `alertdialog`
270
+
271
+ ## 예제
272
+
273
+ ### 올바른 패턴 ✅
274
+
275
+ ```jsx
276
+ // 삼항 연산자로 문자열 변환
277
+ <button aria-expanded={isOpen ? 'true' : 'false'}>토글</button>
278
+
279
+ // String() 함수
280
+ <button aria-pressed={String(isPressed)}>토글</button>
281
+
282
+ // .toString() 메서드
283
+ <div aria-hidden={isHidden.toString()}>내용</div>
284
+
285
+ // 반전된 boolean을 삼항 연산자로
286
+ <button aria-expanded={!isOpen ? 'true' : 'false'}>토글</button>
287
+
288
+ // 비인터랙티브 요소의 정적 ARIA (핸들러 없음)
289
+ <div aria-label="정적 레이블">내용</div>
290
+ ```
291
+
292
+ ### 잘못된 패턴 ❌
293
+
294
+ ```jsx
295
+ // Boolean 직접 바인딩
296
+ <button aria-expanded={isOpen}>토글</button>
297
+ // 수정: aria-expanded={isOpen ? 'true' : 'false'}
298
+
299
+ // 문자열 변환 없는 논리 표현식
300
+ <button aria-expanded={foo && bar}>토글</button>
301
+ // 수정: aria-expanded={foo && bar ? 'true' : 'false'}
302
+
303
+ // 이벤트 핸들러와 정적 값
304
+ <button onClick={handleClick} aria-expanded="false">토글</button>
305
+ // 수정: 동적 상태 바인딩 사용
306
+
307
+ // 인터랙티브 role과 정적 ARIA
308
+ <div role="tab" aria-selected="true">탭</div>
309
+ // 수정: 동적 상태 바인딩 사용
310
+ ```
311
+
312
+ ## 기술 구현
313
+
314
+ ### 기술 스택
315
+
316
+ - **기반:** ESLint 플러그인 (Node.js 환경)
317
+ - **파서:** JSX/TSX AST 파싱을 위한 `@babel/eslint-parser` 또는 TypeScript 파서
318
+ - **분석:** JSX 요소에 대한 Visitor 패턴
319
+
320
+ ### 분석 로직
321
+
322
+ 1. **JSXElement 순회:** `role` 및 `aria-*` 속성 식별
323
+ 2. **스코프 추적:** `context.getScope()`를 사용하여 ARIA 속성에 바인딩된 변수 추적
324
+ 3. **상태 추론:** 값이 `useState` 훅이나 props에서 파생되었는지 판단
325
+ 4. **패턴 검증:** 올바른 문자열 변환 패턴 확인
326
+
327
+ ## 기대 효과
328
+
329
+ ### 1. 접근성 품질 향상
330
+
331
+ 런타임에서만 발견되던 동적 ARIA 오류를 빌드/CI 단계에서 선제적으로 감지
332
+
333
+ ### 2. 개발자 생산성
334
+
335
+ `eslint --fix`로 반복적인 ARIA 상태 바인딩 오류를 자동 수정
336
+
337
+ ### 3. 표준화
338
+
339
+ 프로젝트 전반에 걸쳐 일관된 동적 ARIA 사용 패턴 강제
340
+
341
+ ## 개발
342
+
343
+ ### 테스트 실행
344
+
345
+ ```bash
346
+ npm test
347
+ ```
348
+
349
+ ### 빌드
350
+
351
+ ```bash
352
+ npm run build # 해당되는 경우
353
+ ```
354
+
355
+ ## 기여
356
+
357
+ 기여는 언제나 환영합니다! 이슈나 풀 리퀘스트를 자유롭게 제출해주세요.
358
+
359
+ ## 라이선스
360
+
361
+ ISC
362
+
363
+ ## 키워드
364
+
365
+ - eslint
366
+ - eslint-plugin
367
+ - accessibility
368
+ - a11y
369
+ - aria
370
+ - jsx
371
+ - react
372
+ - state-validation
373
+ - dynamic-attributes
374
+
375
+ ## 관련 프로젝트
376
+
377
+ - [eslint-plugin-jsx-a11y](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y) - 포괄적인 JSX 접근성 검사
378
+ - [axe-core](https://github.com/dequelabs/axe-core) - 런타임 접근성 테스팅 엔진
379
+
380
+ ---
381
+
382
+ **참고:** 이 플러그인은 ARIA 상태 바인딩 패턴의 정적 분석에 집중합니다. 포괄적인 접근성 테스팅을 위해 axe-core 같은 런타임 테스팅 도구와 함께 사용하세요.