Skip to content

Commit 40437b7

Browse files
authored
Merge pull request #26 from osdev-challenge/develop
release: AccessibilityFixer v2.0.0
2 parents 6bfbdad + adaa3c2 commit 40437b7

File tree

66 files changed

+491
-261
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+491
-261
lines changed

.docs/CONTRIBUTING.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Contributing to AccessibilityFixer
2+
3+
We welcome contributions from everyone in the community.
4+
5+
Every contributor must adhere to our **Code of Conduct**.
6+
Please read it carefully to understand what actions are acceptable.
7+
8+
---
9+
10+
## 1. Guiding Principles
11+
12+
AccessibilityFixer is an open source project aiming to make web development more inclusive by providing accessibility checks and AI-powered suggestions.
13+
We value **clarity**, **simplicity**, and **collaboration** over strict rules.
14+
15+
### 1.1 Simplicity
16+
17+
- Keep implementations simple and easy to understand.
18+
- Focus on the most common use cases first.
19+
- Avoid adding too many options unless truly necessary.
20+
21+
### 1.2 Documentation
22+
23+
- Document your changes clearly.
24+
- If you add or modify a command, update the README or relevant docs.
25+
- Screenshots or examples are welcome but optional.
26+
27+
---
28+
29+
## 2. Issues
30+
31+
You can contribute by:
32+
33+
- Reporting bugs
34+
- Suggesting new accessibility rules or improvements
35+
- Improving documentation or examples
36+
- Picking up existing issues from the tracker
37+
38+
---
39+
40+
## 3. Pull Requests
41+
42+
### 3.1 Title Format
43+
44+
Keep PR titles clear and concise. Examples:
45+
46+
- `fix: label detection for input fields`
47+
- `feat: add AI-based suggestion for alt text`
48+
- `docs: update contributing guide`
49+
50+
### 3.2 Checklist
51+
52+
Before opening a PR, please check:
53+
54+
- [ ] Code is easy to read and follows existing style
55+
- [ ] Changes are tested or manually verified
56+
- [ ] Documentation is updated if needed
57+
58+
---
59+
60+
Thank you for helping us improve AccessibilityFixer!

.docs/RULES.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# AccessibilityFixer Rules
2+
3+
AccessibilityFixer detects and fixes accessibility issues in React/JSX/TSX code.
4+
Some rules are resolved automatically with **AI suggestions**, while others are handled with **built-in logic**.
5+
6+
---
7+
8+
## Rules Resolved with AI
9+
10+
These rules are automatically analyzed and fixed using AI-powered suggestions:
11+
12+
| # | Rule Name | Description |
13+
| --- | ----------------------------------------------- | ---------------------------------------------------------------------- |
14+
| 1 | `alt-text` | `<img>`, `<area>`, `<input type="image">` must have an `alt` attribute |
15+
| 2 | `anchor-has-content` | `<a>` elements must have content (avoid empty `<a></a>`) |
16+
| 3 | `aria-props` | Only valid ARIA properties should be used |
17+
| 4 | `aria-role` | Only valid ARIA roles should be used |
18+
| 5 | `control-has-associated-label` | Form control elements must have an associated label |
19+
| 6 | `img-redundant-alt` | Avoid redundant `alt` text like `alt="image"` |
20+
| 7 | `no-interactive-element-to-noninteractive-role` | Interactive elements must not be assigned non-interactive roles |
21+
| 8 | `no-noninteractive-element-to-interactive-role` | Non-interactive elements must not be assigned interactive roles |
22+
| 9 | `form-has-label` | All form controls inside a `<form>` must have labels |
23+
| 10 | `accessible-emoji` | Emojis must have `aria-label` or `role="img"` |
24+
| 11 | `aria-label-is-string` | `aria-label` must always be a string |
25+
| 12 | `no-empty-alt` | Empty `alt=""` is only allowed for decorative images |
26+
| 13 | `form-control-has-label` | All form controls must have visible or screen-reader accessible labels |
27+
28+
---
29+
30+
## Rules Resolved with Built-in Logic
31+
32+
These rules are handled directly through static analysis and logic, without AI involvement:
33+
34+
| # | Rule Name | Description |
35+
| --- | ------------------------------------ | -------------------------------------------------------------------------------- |
36+
| 1 | `anchor-is-valid` | `<a>` elements must have a valid `href` or proper role (e.g., `button`) |
37+
| 2 | `aria-activedescendant-has-tabindex` | Elements using `aria-activedescendant` must have a `tabIndex` |
38+
| 3 | `aria-proptypes` | ARIA property values must have correct types |
39+
| 4 | `aria-unsupported-elements` | Do not use ARIA properties on unsupported elements like `<meta>` or `<script>` |
40+
| 5 | `click-events-have-key-events` | Clickable elements must also support keyboard events |
41+
| 6 | `heading-has-content` | Heading tags (`<h1>` ~ `<h6>`) must not be empty |
42+
| 7 | `html-has-lang` | `<html>` element must have a `lang` attribute |
43+
| 8 | `interactive-supports-focus` | Interactive roles (button, link) must support `tabIndex` |
44+
| 9 | `label-has-associated-control` | `<label>` must be associated with a form control (`for` or nesting) |
45+
| 10 | `mouse-events-have-key-events` | Mouse events like `onMouseOver`/`onMouseOut` must have keyboard equivalents |
46+
| 11 | `no-access-key` | Avoid using `accessKey` (can cause accessibility conflicts) |
47+
| 12 | `no-aria-hidden-on-focusable` | Do not use `aria-hidden="true"` on focusable elements |
48+
| 13 | `no-distracting-elements` | Avoid `<marquee>`, `<blink>`, etc. |
49+
| 14 | `no-noninteractive-tabindex` | Non-interactive elements must not use `tabIndex` |
50+
| 15 | `no-static-element-interactions` | Static elements with event handlers must have an appropriate role |
51+
| 16 | `role-has-required-aria-props` | Roles must include their required ARIA properties |
52+
| 17 | `role-supports-aria-props` | Roles must only use supported ARIA properties |
53+
| 18 | `tabindex-no-positive` | Avoid positive `tabIndex` values (e.g., `tabIndex={1}`) |
54+
| 19 | `prefer-native-elements` | Use native elements instead of ARIA roles (`<a>` instead of `role="link"`, etc.) |
55+
56+
---
57+
58+
## Notes
59+
60+
- **AI-powered rules** provide more flexible and context-aware fixes.
61+
- **Logic-based rules** are handled deterministically by the extension.
62+
- The rule set is continuously expanding as AccessibilityFixer evolves.
63+
64+
We welcome new rule proposals and improvements!
65+
Please check out our [CONTRIBUTING.md](.docs/RULES.md) for details on how to contribute.

.github/pull_request_template.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
1-
## 관련 이슈
2-
[//]: # (해결한 문제를 지정하는 Issue Index에 연결해야 합니다.)
1+
## 📌 Key Changes
32

4-
- Resolves :
5-
6-
## 작업 사항
7-
[//]: # (해당 Pull Request에서 수행한 작업 목록을 제시해야 합니다.)
3+
<!-- Briefly describe the purpose of this PR and what problem it solves. -->
84

9-
-
5+
---
106

11-
## 참고 사항
12-
[//]: # (기능을 만들 때 생긴 이슈에 대해서 다른사람들이 참고해야 할 사항을 적습니다.)
7+
## 🔗 Related Issues
138

9+
<!-- Link the issues this PR addresses (e.g., Closes #123). -->
10+
11+
---
12+
13+
## 🔍 Before & After(Optional)
14+
15+
<!-- If applicable, attach screenshots, GIFs, or code snippets to show the changes. -->
16+
17+
---
18+
19+
## ✅ Checklist
20+
21+
Please make sure the following are true before submitting your PR:
22+
23+
- [ ] task 1
24+
- [ ] task 2

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,14 @@ AccessibilityFixer works out of the box, even if you don’t have `eslint-plugin
6464

6565
## Contributing
6666

67-
Contributions, suggestions, and issue reports are welcome!
68-
Please visit our [GitHub Issues page](https://github.com/osdev-challenge/AccessibilityFixer) to submit feedback or bug reports.
67+
## Contributing
68+
69+
We welcome contributions from everyone in the community.
70+
Whether it’s fixing a bug, improving documentation, or suggesting a new feature — your help makes AccessibilityFixer better for everyone.
71+
72+
For detailed contribution guidelines, please check our [CONTRIBUTING.md](.docs/CONTRIBUTING.md).
73+
74+
📖 For a list of resolved accessibility rules, see [RULES.md](.docs/RULES.md).
6975

7076
---
7177

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "accessibilityfixer",
33
"displayName": "AccessibilityFixer",
44
"description": "Automatically detect and fix common accessibility issues in your React code, with AI-powered suggestions and quick fixes.",
5-
"version": "1.0.1",
5+
"version": "2.0.0",
66
"publisher": "uno-accessibilityfixer",
77
"icon": "images/logo.png",
88
"engines": {

src/ai/aiClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// src/ai/aiClient.ts
21
import axios from 'axios';
32
import * as dotenv from 'dotenv';
43
import * as path from 'path';
@@ -33,7 +32,7 @@ export async function callGpt(prompt: string): Promise<string> {
3332
return data.choices[0].message.content;
3433
}
3534

36-
// // GPT API 요청 및 응답 처리
35+
// // 제미나이 API 요청 및 응답 처리
3736

3837
// import { GoogleGenerativeAI, HarmCategory, HarmBlockThreshold } from '@google/generative-ai';
3938
// import dotenv from 'dotenv';

src/ai/context/extractElementA11yContext.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ export interface ElementA11yContext {
1616
tabIndex?: number | null;
1717
disabled?: boolean;
1818
ariaDisabled?: boolean;
19-
/** 보강: 추가 신호 (optional) */
2019
inputType?: string | null;
2120
contentEditable?: boolean | null;
22-
mediaControls?: boolean | null; // audio/video controls
21+
mediaControls?: boolean | null;
2322
nativeInteractive: boolean;
2423
}
2524

@@ -40,7 +39,6 @@ export function extractElementA11yContext(rc: RuleContext): ElementA11yContext {
4039
let disabled = false;
4140
let ariaDisabled = false;
4241

43-
// 보강: 추가 탐지용
4442
let inputType: string | null = null;
4543
let contentEditable: boolean | null = null;
4644
let mediaControls: boolean | null = null;
@@ -74,12 +72,10 @@ export function extractElementA11yContext(rc: RuleContext): ElementA11yContext {
7472
hasHandlers = true;
7573
}
7674

77-
// 보강: 요소별 추가 신호
7875
if (el === "input" && name === "type" && typeof value === "string") {
7976
inputType = value.toLowerCase();
8077
}
8178
if (name === "contentEditable") {
82-
// true | "true" | ""(존재만) 처리
8379
contentEditable = value === true || value === "true" || value === "";
8480
}
8581
if ((el === "audio" || el === "video") && name === "controls") {
@@ -91,7 +87,6 @@ export function extractElementA11yContext(rc: RuleContext): ElementA11yContext {
9187
}
9288
});
9389

94-
// 네이티브 인터랙티브 추정
9590
const el = elementName.toLowerCase();
9691
const nativeInteractiveNames = new Set(["button", "input", "select", "textarea", "summary"]);
9792

src/ai/context/extractLabelingContext.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
// src/ai/context/extractLabelingContext.ts
21
import { RuleContext } from "../../rules/types";
3-
4-
/** labeling-content 전 규칙에서 공용으로 사용하는 컨텍스트 */
52
export type LabelingContext = {
63
// 위치/범위
74
filePath: string;
@@ -31,7 +28,7 @@ export type LabelingContext = {
3128
emojiSequence?: string[];
3229
};
3330

34-
/** 유틸: JSX 태그 제거 + {…} 표현 최소 제거 후 평문만 남기기 */
31+
/** JSX 태그 제거 + {…} 표현 최소 제거 후 평문만 남기기 */
3532
function stripJsxToText(s: string): string {
3633
// 제거 순서: 주석 -> script/style -> 태그 -> JSX 표현 -> 공백 정리
3734
return s
@@ -45,13 +42,13 @@ function stripJsxToText(s: string): string {
4542
.trim();
4643
}
4744

48-
/** 유틸: 간단한 JSX 시작 태그에서 태그명 추출 */
45+
/** 간단한 JSX 시작 태그에서 태그명 추출 */
4946
function detectTagName(snippet: string): string | undefined {
5047
const m = snippet.match(/<\s*([A-Za-z][\w:-]*)\b/);
5148
return m?.[1]?.toLowerCase();
5249
}
5350

54-
/** 유틸: JSX 속성 파싱(문자열/JSX 표현/불리언 속성 대응, 단순 휴리스틱) */
51+
/** JSX 속성 파싱(문자열/JSX 표현/불리언 속성 대응, 단순 휴리스틱) */
5552
function parseAttributes(snippet: string): Record<string, string | true> {
5653
const attrs: Record<string, string | true> = {};
5754
const openTag = snippet.match(/<\s*[A-Za-z][\w:-]*\b([^>]*)>/);
@@ -86,7 +83,7 @@ function parseAttributes(snippet: string): Record<string, string | true> {
8683
return attrs;
8784
}
8885

89-
/** 유틸: 시작/종료 태그 사이 텍스트(대략) */
86+
/** 시작/종료 태그 사이 텍스트(대략) */
9087
function extractInnerText(snippet: string): string | undefined {
9188
// 단일 태그(img,input 등)는 내부 텍스트 없음
9289
if (/\/\s*>$/.test(snippet)) return undefined;
@@ -100,7 +97,7 @@ function extractInnerText(snippet: string): string | undefined {
10097
return text || undefined;
10198
}
10299

103-
/** 유틸: 파일 전체에서 id -> 텍스트/태그 매핑(얕은 휴리스틱) */
100+
/** 파일 전체에서 id -> 텍스트/태그 매핑(얕은 휴리스틱) */
104101
function buildIdMap(fileCode: string): Record<string, { text?: string; tag?: string }> {
105102
const map: Record<string, { text?: string; tag?: string }> = {};
106103
const tagRegex =
@@ -122,7 +119,7 @@ function buildIdMap(fileCode: string): Record<string, { text?: string; tag?: str
122119
return map;
123120
}
124121

125-
/** 유틸: label htmlFor/for -> label 텍스트 배열 매핑 */
122+
/** label htmlFor/for -> label 텍스트 배열 매핑 */
126123
function buildLabelForMap(fileCode: string): Record<string, string[]> {
127124
const map: Record<string, string[]> = {};
128125
const labelRegex =
@@ -142,7 +139,7 @@ function buildLabelForMap(fileCode: string): Record<string, string[]> {
142139
return map;
143140
}
144141

145-
/** 유틸: control 타입 추정 */
142+
/** control 타입 */
146143
function guessControlType(
147144
tag?: string,
148145
attrs?: Record<string, string | true>
@@ -162,7 +159,7 @@ function guessControlType(
162159
return "other";
163160
}
164161

165-
/** 유틸: 간단한 role 계산 */
162+
/** 간단한 role 계산 */
166163
function computeRole(tag?: string, attrs?: Record<string, string | true>): string | undefined {
167164
const roleAttr = typeof attrs?.role === "string" ? String(attrs!.role) : undefined;
168165
if (roleAttr) return roleAttr;
@@ -179,7 +176,7 @@ function computeRole(tag?: string, attrs?: Record<string, string | true>): strin
179176
return undefined;
180177
}
181178

182-
/** 유틸: 파일명 기반 텍스트 */
179+
/** 파일명 기반 텍스트 */
183180
function baseNameFromSrc(src?: string): string | undefined {
184181
if (!src || typeof src !== "string") return undefined;
185182
const m = src.match(/[^/\\]+$/);
@@ -188,7 +185,7 @@ function baseNameFromSrc(src?: string): string | undefined {
188185
return name.replace(/[_\-]+/g, " ").trim();
189186
}
190187

191-
/** 유틸: 인근 figcaption 텍스트(±10줄) */
188+
/** 인근 figcaption 텍스트(±10줄) */
192189
function findNearbyFigcaption(lines: string[], lineNumber: number): string | undefined {
193190
const start = Math.max(0, lineNumber - 10);
194191
const end = Math.min(lines.length - 1, lineNumber + 10);
@@ -199,15 +196,15 @@ function findNearbyFigcaption(lines: string[], lineNumber: number): string | und
199196
return text || undefined;
200197
}
201198

202-
/** 유틸: 주변에 form이 있는지(±20줄) */
199+
/** 주변에 form이 있는지(±20줄) */
203200
function isNearForm(lines: string[], lineNumber: number): boolean {
204201
const start = Math.max(0, lineNumber - 20);
205202
const end = Math.min(lines.length - 1, lineNumber + 20);
206203
const slice = lines.slice(start, end + 1).join("\n");
207204
return /<\s*form\b/i.test(slice);
208205
}
209206

210-
/** 유틸: 이모지 시퀀스 추출(간단 범위 기반) */
207+
/** 이모지 시퀀스 추출(간단 범위 기반) */
211208
function extractEmojis(text?: string): string[] | undefined {
212209
if (!text) return undefined;
213210
// BMP 기반 + 일부 확장 — 실용적 커버리지
@@ -216,7 +213,7 @@ function extractEmojis(text?: string): string[] | undefined {
216213
return list.length ? list : undefined;
217214
}
218215

219-
/** 유틸: aria-labelledby -> 연결 텍스트 생성 */
216+
/** aria-labelledby -> 연결 텍스트 생성 */
220217
function computeAssociatedLabelText(
221218
attrs: Record<string, string | true> | undefined,
222219
idMap: Record<string, { text?: string; tag?: string }> | undefined,
@@ -239,7 +236,7 @@ function computeAssociatedLabelText(
239236
return undefined;
240237
}
241238

242-
/** 메인: RuleContext -> LabelingContext */
239+
/** RuleContext -> LabelingContext */
243240
export function extractLabelingContext(rc: RuleContext): LabelingContext {
244241
const { document, fileCode, code, lineNumber } = rc;
245242

src/ai/pipelines/codeActions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// src/ai/pipelines/codeActions.ts
21
import * as vscode from "vscode";
32
import { RuleContext } from "../../rules/types";
43

@@ -24,7 +23,7 @@ export function createReplaceAction(
2423
return [action];
2524
}
2625

27-
/* ===================== B안: 요소 전체 교체용 ===================== */
26+
/* ===================== 요소 전체 교체용 ===================== */
2827

2928
/**
3029
* 요소 전체(elementRange)를 새 HTML로 한 번에 교체하는 QuickFix

0 commit comments

Comments
 (0)