swwa-mcp 0.1.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 +21 -0
- package/README.md +50 -0
- package/assets/alt-text-ko.json +7 -0
- package/assets/axe-rule-map.json +196 -0
- package/assets/certification.json +47 -0
- package/assets/kwcag22-checklist.md +123 -0
- package/assets/kwcag22.json +1688 -0
- package/assets/link-text-ko.json +8 -0
- package/assets/mobile-app-2.0.md +38 -0
- package/assets/sources.json +68 -0
- package/assets/wcag22.json +780 -0
- package/bin/swwa-mcp.mjs +15 -0
- package/dist/color/contrast.js +175 -0
- package/dist/color/contrast.js.map +1 -0
- package/dist/data/axe-map.js +32 -0
- package/dist/data/axe-map.js.map +1 -0
- package/dist/data/certification.js +54 -0
- package/dist/data/certification.js.map +1 -0
- package/dist/data/kwcag22.js +91 -0
- package/dist/data/kwcag22.js.map +1 -0
- package/dist/data/loader.js +18 -0
- package/dist/data/loader.js.map +1 -0
- package/dist/data/sources.js +44 -0
- package/dist/data/sources.js.map +1 -0
- package/dist/data/wcag22.js +44 -0
- package/dist/data/wcag22.js.map +1 -0
- package/dist/data/wordlists.js +40 -0
- package/dist/data/wordlists.js.map +1 -0
- package/dist/engine/browser-detect.js +44 -0
- package/dist/engine/browser-detect.js.map +1 -0
- package/dist/engine/browser.js +114 -0
- package/dist/engine/browser.js.map +1 -0
- package/dist/engine/static-worker.js +18 -0
- package/dist/engine/static-worker.js.map +1 -0
- package/dist/engine/static.js +139 -0
- package/dist/engine/static.js.map +1 -0
- package/dist/engine/url-guard.js +37 -0
- package/dist/engine/url-guard.js.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.js +176 -0
- package/dist/messages.js.map +1 -0
- package/dist/normalize/axe.js +72 -0
- package/dist/normalize/axe.js.map +1 -0
- package/dist/normalize/checkpoints.js +47 -0
- package/dist/normalize/checkpoints.js.map +1 -0
- package/dist/normalize/finding.js +24 -0
- package/dist/normalize/finding.js.map +1 -0
- package/dist/normalize/locale.js +20 -0
- package/dist/normalize/locale.js.map +1 -0
- package/dist/prompts/audit-report.js +45 -0
- package/dist/prompts/audit-report.js.map +1 -0
- package/dist/prompts/review-markup.js +35 -0
- package/dist/prompts/review-markup.js.map +1 -0
- package/dist/report/cert.js +72 -0
- package/dist/report/cert.js.map +1 -0
- package/dist/report/format.js +36 -0
- package/dist/report/format.js.map +1 -0
- package/dist/report/summarize.js +61 -0
- package/dist/report/summarize.js.map +1 -0
- package/dist/report/types.js +2 -0
- package/dist/report/types.js.map +1 -0
- package/dist/resources/index.js +69 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/rules/b/b-focus-order.js +33 -0
- package/dist/rules/b/b-focus-order.js.map +1 -0
- package/dist/rules/b/b-focus-visible.js +71 -0
- package/dist/rules/b/b-focus-visible.js.map +1 -0
- package/dist/rules/b/b-keyboard-reachable.js +82 -0
- package/dist/rules/b/b-keyboard-reachable.js.map +1 -0
- package/dist/rules/b/b-motion-runtime.js +50 -0
- package/dist/rules/b/b-motion-runtime.js.map +1 -0
- package/dist/rules/b/b-skip-link-works.js +58 -0
- package/dist/rules/b/b-skip-link-works.js.map +1 -0
- package/dist/rules/b/b-target-size-6mm.js +54 -0
- package/dist/rules/b/b-target-size-6mm.js.map +1 -0
- package/dist/rules/b/index.js +7 -0
- package/dist/rules/b/index.js.map +1 -0
- package/dist/rules/k/index.js +33 -0
- package/dist/rules/k/index.js.map +1 -0
- package/dist/rules/k/k-accesskey.js +20 -0
- package/dist/rules/k/k-accesskey.js.map +1 -0
- package/dist/rules/k/k-alt-meaningless.js +35 -0
- package/dist/rules/k/k-alt-meaningless.js.map +1 -0
- package/dist/rules/k/k-autocomplete-missing.js +36 -0
- package/dist/rules/k/k-autocomplete-missing.js.map +1 -0
- package/dist/rules/k/k-autoplay-media.js +30 -0
- package/dist/rules/k/k-autoplay-media.js.map +1 -0
- package/dist/rules/k/k-captcha-detect.js +24 -0
- package/dist/rules/k/k-captcha-detect.js.map +1 -0
- package/dist/rules/k/k-carousel-no-pause.js +33 -0
- package/dist/rules/k/k-carousel-no-pause.js.map +1 -0
- package/dist/rules/k/k-device-motion.js +22 -0
- package/dist/rules/k/k-device-motion.js.map +1 -0
- package/dist/rules/k/k-down-event-action.js +19 -0
- package/dist/rules/k/k-down-event-action.js.map +1 -0
- package/dist/rules/k/k-error-association.js +31 -0
- package/dist/rules/k/k-error-association.js.map +1 -0
- package/dist/rules/k/k-flash-animation.js +57 -0
- package/dist/rules/k/k-flash-animation.js.map +1 -0
- package/dist/rules/k/k-gesture-listener.js +30 -0
- package/dist/rules/k/k-gesture-listener.js.map +1 -0
- package/dist/rules/k/k-iframe-title.js +23 -0
- package/dist/rules/k/k-iframe-title.js.map +1 -0
- package/dist/rules/k/k-lang-ko-expected.js +29 -0
- package/dist/rules/k/k-lang-ko-expected.js.map +1 -0
- package/dist/rules/k/k-link-same-text-diff-href.js +41 -0
- package/dist/rules/k/k-link-same-text-diff-href.js.map +1 -0
- package/dist/rules/k/k-link-text-generic.js +29 -0
- package/dist/rules/k/k-link-text-generic.js.map +1 -0
- package/dist/rules/k/k-media-track.js +22 -0
- package/dist/rules/k/k-media-track.js.map +1 -0
- package/dist/rules/k/k-mouse-only-handler.js +27 -0
- package/dist/rules/k/k-mouse-only-handler.js.map +1 -0
- package/dist/rules/k/k-new-window-notice.js +27 -0
- package/dist/rules/k/k-new-window-notice.js.map +1 -0
- package/dist/rules/k/k-onload-popup.js +30 -0
- package/dist/rules/k/k-onload-popup.js.map +1 -0
- package/dist/rules/k/k-outline-none.js +55 -0
- package/dist/rules/k/k-outline-none.js.map +1 -0
- package/dist/rules/k/k-parse-errors.js +38 -0
- package/dist/rules/k/k-parse-errors.js.map +1 -0
- package/dist/rules/k/k-placeholder-only-label.js +32 -0
- package/dist/rules/k/k-placeholder-only-label.js.map +1 -0
- package/dist/rules/k/k-select-onchange.js +19 -0
- package/dist/rules/k/k-select-onchange.js.map +1 -0
- package/dist/rules/k/k-sensory-instruction.js +32 -0
- package/dist/rules/k/k-sensory-instruction.js.map +1 -0
- package/dist/rules/k/k-session-timeout-hint.js +29 -0
- package/dist/rules/k/k-session-timeout-hint.js.map +1 -0
- package/dist/rules/k/k-skip-link-first.js +23 -0
- package/dist/rules/k/k-skip-link-first.js.map +1 -0
- package/dist/rules/k/k-skip-target-exists.js +28 -0
- package/dist/rules/k/k-skip-target-exists.js.map +1 -0
- package/dist/rules/k/k-tabindex-positive.js +23 -0
- package/dist/rules/k/k-tabindex-positive.js.map +1 -0
- package/dist/rules/k/k-table-caption.js +26 -0
- package/dist/rules/k/k-table-caption.js.map +1 -0
- package/dist/rules/k/k-table-th-missing.js +28 -0
- package/dist/rules/k/k-table-th-missing.js.map +1 -0
- package/dist/rules/k/k-title-generic.js +35 -0
- package/dist/rules/k/k-title-generic.js.map +1 -0
- package/dist/rules/registry.js +62 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/rules/types.js +2 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/util/browser-dom.js +50 -0
- package/dist/rules/util/browser-dom.js.map +1 -0
- package/dist/rules/util/focusable.js +29 -0
- package/dist/rules/util/focusable.js.map +1 -0
- package/dist/rules/util/selector.js +19 -0
- package/dist/rules/util/selector.js.map +1 -0
- package/dist/rules/util/text.js +13 -0
- package/dist/rules/util/text.js.map +1 -0
- package/dist/schema-dialect.js +41 -0
- package/dist/schema-dialect.js.map +1 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/audit-url.js +109 -0
- package/dist/tools/audit-url.js.map +1 -0
- package/dist/tools/browser-status.js +29 -0
- package/dist/tools/browser-status.js.map +1 -0
- package/dist/tools/cert-readiness.js +76 -0
- package/dist/tools/cert-readiness.js.map +1 -0
- package/dist/tools/check-contrast.js +58 -0
- package/dist/tools/check-contrast.js.map +1 -0
- package/dist/tools/check-html.js +88 -0
- package/dist/tools/check-html.js.map +1 -0
- package/dist/tools/checklist.js +94 -0
- package/dist/tools/checklist.js.map +1 -0
- package/dist/tools/lookup.js +70 -0
- package/dist/tools/lookup.js.map +1 -0
- package/dist/tools/report-schema.js +49 -0
- package/dist/tools/report-schema.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { errorAssociation } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-error-association",
|
|
6
|
+
kwcag: "7.3.1",
|
|
7
|
+
wcag: ["3.3.1"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "moderate",
|
|
10
|
+
confidence: "medium",
|
|
11
|
+
tier: "T2",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
for (const el of Array.from(ctx.document.querySelectorAll('[aria-invalid="true"]'))) {
|
|
15
|
+
const describedby = (el.getAttribute("aria-describedby") ?? "").trim();
|
|
16
|
+
let associated = false;
|
|
17
|
+
if (describedby !== "") {
|
|
18
|
+
// 참조 id가 실제로 존재하고 내용이 있으면 연결된 것으로 본다.
|
|
19
|
+
associated = describedby.split(/\s+/).some((id) => {
|
|
20
|
+
const target = ctx.document.getElementById(id);
|
|
21
|
+
return target !== null && normText(target.textContent ?? "") !== "";
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (!associated) {
|
|
25
|
+
findings.push(createFinding(rule, el, { message: errorAssociation.message, fix: errorAssociation.fix, outcome: "incomplete" }));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return findings;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=k-error-association.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-error-association.js","sourceRoot":"","sources":["../../../src/rules/k/k-error-association.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIrD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAC;YACpF,MAAM,WAAW,GAAG,CAAC,EAAE,CAAC,YAAY,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACvE,IAAI,UAAU,GAAG,KAAK,CAAC;YACvB,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;gBACvB,sCAAsC;gBACtC,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;oBAChD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;oBAC/C,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;gBACtE,CAAC,CAAC,CAAC;YACL,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CACjH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { flashAnimation } from "../../messages.js";
|
|
3
|
+
/** "0.2s"·"200ms" 같은 시간 토큰을 초 단위로 파싱. 없으면 null. */
|
|
4
|
+
function parseSeconds(token) {
|
|
5
|
+
const m = /(\d*\.?\d+)\s*(ms|s)\b/.exec(token);
|
|
6
|
+
if (!m)
|
|
7
|
+
return null;
|
|
8
|
+
const v = parseFloat(m[1]);
|
|
9
|
+
return m[2] === "ms" ? v / 1000 : v;
|
|
10
|
+
}
|
|
11
|
+
const FLASH_THRESHOLD_S = 0.33; // 초당 3회 이상 → 주기 0.33초 미만
|
|
12
|
+
export const rule = {
|
|
13
|
+
id: "k-flash-animation",
|
|
14
|
+
kwcag: "6.3.1",
|
|
15
|
+
wcag: ["2.3.1"],
|
|
16
|
+
engine: "k",
|
|
17
|
+
impact: "serious",
|
|
18
|
+
confidence: "low",
|
|
19
|
+
tier: "T2",
|
|
20
|
+
run(ctx) {
|
|
21
|
+
const findings = [];
|
|
22
|
+
const sheets = ctx.document.styleSheets;
|
|
23
|
+
for (let i = 0; i < sheets.length; i++) {
|
|
24
|
+
let rules;
|
|
25
|
+
try {
|
|
26
|
+
rules = sheets[i]?.cssRules;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (!rules)
|
|
32
|
+
continue;
|
|
33
|
+
for (let j = 0; j < rules.length; j++) {
|
|
34
|
+
const r = rules[j];
|
|
35
|
+
if (!r?.style)
|
|
36
|
+
continue;
|
|
37
|
+
const shorthand = (r.style.animation ?? "").toLowerCase();
|
|
38
|
+
const iter = (r.style.animationIterationCount ?? "").toLowerCase();
|
|
39
|
+
const durLong = r.style.animationDuration ?? "";
|
|
40
|
+
const infinite = shorthand.includes("infinite") || iter.includes("infinite");
|
|
41
|
+
if (!infinite)
|
|
42
|
+
continue;
|
|
43
|
+
const seconds = parseSeconds(durLong) ?? parseSeconds(shorthand);
|
|
44
|
+
if (seconds !== null && seconds > 0 && seconds < FLASH_THRESHOLD_S) {
|
|
45
|
+
findings.push(createFinding(rule, null, {
|
|
46
|
+
message: flashAnimation.message,
|
|
47
|
+
fix: flashAnimation.fix,
|
|
48
|
+
outcome: "incomplete",
|
|
49
|
+
selectorOverride: r.selectorText ?? "(animation)",
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return findings;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=k-flash-animation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-flash-animation.js","sourceRoot":"","sources":["../../../src/rules/k/k-flash-animation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AASnD,mDAAmD;AACnD,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,CAAC,GAAG,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,CAAC,yBAAyB;AAEzD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,IAAI,KAA6B,CAAC;YAClC,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,QAA6C,CAAC;YACnE,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACnB,IAAI,CAAC,CAAC,EAAE,KAAK;oBAAE,SAAS;gBACxB,MAAM,SAAS,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;gBAC1D,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;gBACnE,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC;gBAChD,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC7E,IAAI,CAAC,QAAQ;oBAAE,SAAS;gBACxB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,SAAS,CAAC,CAAC;gBACjE,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,iBAAiB,EAAE,CAAC;oBACnE,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE;wBACxB,OAAO,EAAE,cAAc,CAAC,OAAO;wBAC/B,GAAG,EAAE,cAAc,CAAC,GAAG;wBACvB,OAAO,EAAE,YAAY;wBACrB,gBAAgB,EAAE,CAAC,CAAC,YAAY,IAAI,aAAa;qBAClD,CAAC,CACH,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { gestureListener } from "../../messages.js";
|
|
3
|
+
// 다중 포인터·경로 기반 제스처 처리 흔적(인라인 핸들러 속성 + 인라인/헤드 스크립트 텍스트).
|
|
4
|
+
const GESTURE = /touchmove|ontouchmove|pointermove|onpointermove|gesturestart|gesturechange|pinch|swipe|hammer/i;
|
|
5
|
+
export const rule = {
|
|
6
|
+
id: "k-gesture-listener",
|
|
7
|
+
kwcag: "6.5.1",
|
|
8
|
+
wcag: ["2.5.1"],
|
|
9
|
+
engine: "k",
|
|
10
|
+
impact: "moderate",
|
|
11
|
+
confidence: "low",
|
|
12
|
+
tier: "T2",
|
|
13
|
+
run(ctx) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
// 1) 인라인 이벤트 핸들러 속성
|
|
16
|
+
for (const el of Array.from(ctx.document.querySelectorAll("[ontouchmove],[onpointermove],[ongesturestart],[ongesturechange]"))) {
|
|
17
|
+
findings.push(createFinding(rule, el, { message: gestureListener.message, fix: gestureListener.fix, outcome: "incomplete" }));
|
|
18
|
+
}
|
|
19
|
+
// 2) 스크립트 텍스트(주입 안 함 — 문자열만 검사)
|
|
20
|
+
for (const script of Array.from(ctx.document.querySelectorAll("script"))) {
|
|
21
|
+
const code = script.textContent ?? "";
|
|
22
|
+
if (code && GESTURE.test(code)) {
|
|
23
|
+
findings.push(createFinding(rule, script, { message: gestureListener.message, fix: gestureListener.fix, outcome: "incomplete" }));
|
|
24
|
+
break; // 스크립트 다건 중복 방지
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return findings;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=k-gesture-listener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-gesture-listener.js","sourceRoot":"","sources":["../../../src/rules/k/k-gesture-listener.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,wDAAwD;AACxD,MAAM,OAAO,GAAG,gGAAgG,CAAC;AAEjH,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,oBAAoB;QACpB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,kEAAkE,CAAC,CAAC,EAAE,CAAC;YAC/H,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAC/G,CAAC;QACJ,CAAC;QAED,gCAAgC;QAChC,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzE,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;YACtC,IAAI,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/B,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CACnH,CAAC;gBACF,MAAM,CAAC,gBAAgB;YACzB,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { iframeTitle } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-iframe-title",
|
|
6
|
+
kwcag: "6.4.2",
|
|
7
|
+
wcag: ["2.4.2"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "serious",
|
|
10
|
+
confidence: "high",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
for (const iframe of Array.from(ctx.document.querySelectorAll("iframe"))) {
|
|
15
|
+
const title = normText(iframe.getAttribute("title") ?? "");
|
|
16
|
+
if (title === "") {
|
|
17
|
+
findings.push(createFinding(rule, iframe, { message: iframeTitle.message, fix: iframeTitle.fix, outcome: "fail" }));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return findings;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=k-iframe-title.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-iframe-title.js","sourceRoot":"","sources":["../../../src/rules/k/k-iframe-title.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzE,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3D,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;gBACjB,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACtH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText, hangulRatio } from "../util/text.js";
|
|
3
|
+
import { langKoExpected } from "../../messages.js";
|
|
4
|
+
const HANGUL_THRESHOLD = 0.3;
|
|
5
|
+
export const rule = {
|
|
6
|
+
id: "k-lang-ko-expected",
|
|
7
|
+
kwcag: "7.1.1",
|
|
8
|
+
wcag: ["3.1.1"],
|
|
9
|
+
engine: "k",
|
|
10
|
+
impact: "serious",
|
|
11
|
+
confidence: "medium",
|
|
12
|
+
tier: "T1",
|
|
13
|
+
run(ctx) {
|
|
14
|
+
const lang = (ctx.document.documentElement.getAttribute("lang") ?? "").toLowerCase();
|
|
15
|
+
if (lang.startsWith("ko"))
|
|
16
|
+
return [];
|
|
17
|
+
const text = normText(ctx.document.body?.textContent ?? "");
|
|
18
|
+
if (hangulRatio(text) < HANGUL_THRESHOLD)
|
|
19
|
+
return [];
|
|
20
|
+
return [
|
|
21
|
+
createFinding(rule, ctx.document.documentElement, {
|
|
22
|
+
message: langKoExpected.message(lang || "(없음)"),
|
|
23
|
+
fix: langKoExpected.fix,
|
|
24
|
+
outcome: "fail",
|
|
25
|
+
}),
|
|
26
|
+
];
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=k-lang-ko-expected.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-lang-ko-expected.js","sourceRoot":"","sources":["../../../src/rules/k/k-lang-ko-expected.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAInD,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACrF,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC;QAC5D,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,gBAAgB;YAAE,OAAO,EAAE,CAAC;QACpD,OAAO;YACL,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,eAAe,EAAE;gBAChD,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;gBAC/C,GAAG,EAAE,cAAc,CAAC,GAAG;gBACvB,OAAO,EAAE,MAAM;aAChB,CAAC;SACH,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { linkSameTextDiffHref } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-link-same-text-diff-href",
|
|
6
|
+
kwcag: "6.4.3",
|
|
7
|
+
wcag: ["2.4.4"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "moderate",
|
|
10
|
+
confidence: "low",
|
|
11
|
+
tier: "T2",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
// 같은 (정규화된) 접근명 텍스트가 서로 다른 href를 가리키면 후보. aria-label로 구별되면 그 값을 이름으로 본다.
|
|
14
|
+
const byText = new Map();
|
|
15
|
+
for (const a of Array.from(ctx.document.querySelectorAll("a[href]"))) {
|
|
16
|
+
const name = normText(a.getAttribute("aria-label") ?? a.textContent ?? "");
|
|
17
|
+
if (name === "")
|
|
18
|
+
continue;
|
|
19
|
+
const href = a.getAttribute("href") ?? "";
|
|
20
|
+
const entry = byText.get(name);
|
|
21
|
+
if (entry) {
|
|
22
|
+
entry.hrefs.add(href);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
byText.set(name, { hrefs: new Set([href]), first: a });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const findings = [];
|
|
29
|
+
for (const [name, entry] of byText) {
|
|
30
|
+
if (entry.hrefs.size > 1) {
|
|
31
|
+
findings.push(createFinding(rule, entry.first, {
|
|
32
|
+
message: linkSameTextDiffHref.message(name),
|
|
33
|
+
fix: linkSameTextDiffHref.fix,
|
|
34
|
+
outcome: "incomplete",
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return findings;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=k-link-same-text-diff-href.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-link-same-text-diff-href.js","sourceRoot":"","sources":["../../../src/rules/k/k-link-same-text-diff-href.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAIzD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,4BAA4B;IAChC,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,yEAAyE;QACzE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkD,CAAC;QACzE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAC3E,IAAI,IAAI,KAAK,EAAE;gBAAE,SAAS;YAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;QACD,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE;oBAC/B,OAAO,EAAE,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC;oBAC3C,GAAG,EAAE,oBAAoB,CAAC,GAAG;oBAC7B,OAAO,EAAE,YAAY;iBACtB,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { linkTextGeneric } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-link-text-generic",
|
|
6
|
+
kwcag: "6.4.3",
|
|
7
|
+
wcag: ["2.4.4"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "moderate",
|
|
10
|
+
confidence: "medium",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
const generic = ctx.data.wordlists.linkText.generic.map((w) => w.toLowerCase());
|
|
15
|
+
for (const a of Array.from(ctx.document.querySelectorAll("a[href]"))) {
|
|
16
|
+
const ariaLabel = normText(a.getAttribute("aria-label") ?? "");
|
|
17
|
+
if (ariaLabel !== "")
|
|
18
|
+
continue; // aria-label로 보완되면 통과
|
|
19
|
+
const rawText = normText(a.textContent ?? "");
|
|
20
|
+
if (rawText === "")
|
|
21
|
+
continue;
|
|
22
|
+
if (generic.includes(rawText.toLowerCase())) {
|
|
23
|
+
findings.push(createFinding(rule, a, { message: linkTextGeneric.message(rawText), fix: linkTextGeneric.fix, outcome: "fail" }));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return findings;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=k-link-text-generic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-link-text-generic.js","sourceRoot":"","sources":["../../../src/rules/k/k-link-text-generic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAChF,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACrE,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/D,IAAI,SAAS,KAAK,EAAE;gBAAE,SAAS,CAAC,sBAAsB;YACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAC9C,IAAI,OAAO,KAAK,EAAE;gBAAE,SAAS;YAC7B,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBAC5C,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CACjH,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { mediaTrack } from "../../messages.js";
|
|
3
|
+
export const rule = {
|
|
4
|
+
id: "k-media-track",
|
|
5
|
+
kwcag: "5.2.1",
|
|
6
|
+
wcag: ["1.2.1", "1.2.2", "1.2.3"],
|
|
7
|
+
engine: "k",
|
|
8
|
+
impact: "serious",
|
|
9
|
+
confidence: "medium",
|
|
10
|
+
tier: "T1",
|
|
11
|
+
run(ctx) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const video of Array.from(ctx.document.querySelectorAll("video"))) {
|
|
14
|
+
const hasCaptions = Array.from(video.querySelectorAll("track")).some((t) => (t.getAttribute("kind") ?? "").toLowerCase() === "captions");
|
|
15
|
+
if (!hasCaptions) {
|
|
16
|
+
findings.push(createFinding(rule, video, { message: mediaTrack.message, fix: mediaTrack.fix, outcome: "fail" }));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return findings;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=k-media-track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-media-track.js","sourceRoot":"","sources":["../../../src/rules/k/k-media-track.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAI/C,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IACjC,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,SAAS;IACjB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;YACvE,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAClE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,UAAU,CACnE,CAAC;YACF,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACnH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { mouseOnlyHandler } from "../../messages.js";
|
|
3
|
+
const NATIVELY_INTERACTIVE_TAGS = new Set(["a", "button", "select", "textarea", "input", "label"]);
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-mouse-only-handler",
|
|
6
|
+
kwcag: "6.1.1",
|
|
7
|
+
wcag: ["2.1.1"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "critical",
|
|
10
|
+
confidence: "medium",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
for (const el of Array.from(ctx.document.querySelectorAll("[onclick], [onmouseover]"))) {
|
|
15
|
+
const tag = el.tagName.toLowerCase();
|
|
16
|
+
if (NATIVELY_INTERACTIVE_TAGS.has(tag))
|
|
17
|
+
continue;
|
|
18
|
+
const hasRole = el.getAttribute("role") !== null;
|
|
19
|
+
const hasTabindex = el.getAttribute("tabindex") !== null;
|
|
20
|
+
if (hasRole && hasTabindex)
|
|
21
|
+
continue; // 키보드 접근 가능하도록 보완된 것으로 본다
|
|
22
|
+
findings.push(createFinding(rule, el, { message: mouseOnlyHandler.message, fix: mouseOnlyHandler.fix, outcome: "fail" }));
|
|
23
|
+
}
|
|
24
|
+
return findings;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=k-mouse-only-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-mouse-only-handler.js","sourceRoot":"","sources":["../../../src/rules/k/k-mouse-only-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIrD,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnG,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,sBAAsB;IAC1B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC;YACvF,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YACrC,IAAI,yBAAyB,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YACjD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;YACjD,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;YACzD,IAAI,OAAO,IAAI,WAAW;gBAAE,SAAS,CAAC,0BAA0B;YAChE,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAC3G,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { newWindowNotice } from "../../messages.js";
|
|
4
|
+
const NOTICE_PATTERN = /새\s*창|새창|새\s*탭|new window/i;
|
|
5
|
+
export const rule = {
|
|
6
|
+
id: "k-new-window-notice",
|
|
7
|
+
kwcag: "7.2.1",
|
|
8
|
+
wcag: ["3.2.1", "3.2.2"],
|
|
9
|
+
engine: "k",
|
|
10
|
+
impact: "moderate",
|
|
11
|
+
confidence: "medium",
|
|
12
|
+
tier: "T1",
|
|
13
|
+
run(ctx) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
for (const a of Array.from(ctx.document.querySelectorAll('a[target="_blank"]'))) {
|
|
16
|
+
const text = normText(a.textContent ?? "");
|
|
17
|
+
const ariaLabel = a.getAttribute("aria-label") ?? "";
|
|
18
|
+
const title = a.getAttribute("title") ?? "";
|
|
19
|
+
const combined = `${text} ${ariaLabel} ${title}`;
|
|
20
|
+
if (!NOTICE_PATTERN.test(combined)) {
|
|
21
|
+
findings.push(createFinding(rule, a, { message: newWindowNotice.message, fix: newWindowNotice.fix, outcome: "fail" }));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return findings;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=k-new-window-notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-new-window-notice.js","sourceRoot":"","sources":["../../../src/rules/k/k-new-window-notice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,MAAM,cAAc,GAAG,4BAA4B,CAAC;AAEpD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,qBAAqB;IACzB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC;YAChF,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;YACrD,MAAM,KAAK,GAAG,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,SAAS,IAAI,KAAK,EAAE,CAAC;YACjD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnC,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,eAAe,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CACxG,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { onloadPopup } from "../../messages.js";
|
|
3
|
+
const WINDOW_OPEN = /window\.open\s*\(/;
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-onload-popup",
|
|
6
|
+
kwcag: "7.2.1",
|
|
7
|
+
wcag: ["3.2.1"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "moderate",
|
|
10
|
+
confidence: "low",
|
|
11
|
+
tier: "T2",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
// 1) <body onload="window.open(...)"> 등 인라인 로드 핸들러
|
|
14
|
+
const bodyOnload = ctx.document.body?.getAttribute("onload") ?? "";
|
|
15
|
+
if (WINDOW_OPEN.test(bodyOnload)) {
|
|
16
|
+
return [createFinding(rule, ctx.document.body, { message: onloadPopup.message, fix: onloadPopup.fix, outcome: "incomplete" })];
|
|
17
|
+
}
|
|
18
|
+
// 2) 스크립트 텍스트에서 로드 시점 window.open (onload/DOMContentLoaded/즉시 실행 근처)
|
|
19
|
+
for (const script of Array.from(ctx.document.querySelectorAll("script"))) {
|
|
20
|
+
const code = script.textContent ?? "";
|
|
21
|
+
if (!code || !WINDOW_OPEN.test(code))
|
|
22
|
+
continue;
|
|
23
|
+
if (/onload|DOMContentLoaded|addEventListener\s*\(\s*['"]load['"]/.test(code) || /^\s*window\.open/m.test(code)) {
|
|
24
|
+
return [createFinding(rule, script, { message: onloadPopup.message, fix: onloadPopup.fix, outcome: "incomplete" })];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=k-onload-popup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-onload-popup.js","sourceRoot":"","sources":["../../../src/rules/k/k-onload-popup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhD,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,mDAAmD;QACnD,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnE,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACjI,CAAC;QACD,qEAAqE;QACrE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzE,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;YACtC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC/C,IAAI,8DAA8D,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChH,OAAO,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;YACtH,CAAC;QACH,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { outlineNone } from "../../messages.js";
|
|
3
|
+
function removesOutline(style) {
|
|
4
|
+
const outline = (style.outline ?? "").toLowerCase();
|
|
5
|
+
const width = (style.outlineWidth ?? "").toLowerCase();
|
|
6
|
+
const st = (style.outlineStyle ?? "").toLowerCase();
|
|
7
|
+
return outline === "none" || outline === "0" || outline.startsWith("0 ") || width === "0" || width === "0px" || st === "none";
|
|
8
|
+
}
|
|
9
|
+
function hasAlternative(style) {
|
|
10
|
+
const box = (style.boxShadow ?? "").trim();
|
|
11
|
+
const border = (style.border ?? "").trim();
|
|
12
|
+
const borderColor = (style.borderColor ?? "").trim();
|
|
13
|
+
const borderWidth = (style.borderWidth ?? "").trim();
|
|
14
|
+
return (box !== "" && box !== "none") || border !== "" || borderColor !== "" || (borderWidth !== "" && borderWidth !== "0");
|
|
15
|
+
}
|
|
16
|
+
export const rule = {
|
|
17
|
+
id: "k-outline-none",
|
|
18
|
+
kwcag: "6.1.2",
|
|
19
|
+
wcag: ["2.4.7"],
|
|
20
|
+
engine: "k",
|
|
21
|
+
impact: "serious",
|
|
22
|
+
confidence: "low",
|
|
23
|
+
tier: "T2",
|
|
24
|
+
run(ctx) {
|
|
25
|
+
const findings = [];
|
|
26
|
+
const sheets = ctx.document.styleSheets;
|
|
27
|
+
for (let i = 0; i < sheets.length; i++) {
|
|
28
|
+
let rules;
|
|
29
|
+
try {
|
|
30
|
+
rules = sheets[i]?.cssRules;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
continue; // 접근 불가한 시트는 건너뛴다
|
|
34
|
+
}
|
|
35
|
+
if (!rules)
|
|
36
|
+
continue;
|
|
37
|
+
for (let j = 0; j < rules.length; j++) {
|
|
38
|
+
const r = rules[j];
|
|
39
|
+
const selector = r?.selectorText ?? "";
|
|
40
|
+
if (!selector.includes(":focus") || !r?.style)
|
|
41
|
+
continue;
|
|
42
|
+
if (removesOutline(r.style) && !hasAlternative(r.style)) {
|
|
43
|
+
findings.push(createFinding(rule, null, {
|
|
44
|
+
message: outlineNone.message,
|
|
45
|
+
fix: outlineNone.fix,
|
|
46
|
+
outcome: "incomplete",
|
|
47
|
+
selectorOverride: selector,
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return findings;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=k-outline-none.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-outline-none.js","sourceRoot":"","sources":["../../../src/rules/k/k-outline-none.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parse } from "parse5";
|
|
2
|
+
import { parseErrors as parseErrorMessages } from "../../messages.js";
|
|
3
|
+
const WHITELIST = new Set(Object.keys(parseErrorMessages));
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-parse-errors",
|
|
6
|
+
kwcag: "8.1.1",
|
|
7
|
+
wcag: ["4.1.1"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "minor",
|
|
10
|
+
confidence: "high",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
parse(ctx.html, {
|
|
15
|
+
sourceCodeLocationInfo: true,
|
|
16
|
+
onParseError: (error) => {
|
|
17
|
+
if (!WHITELIST.has(error.code))
|
|
18
|
+
return; // 무해한 코드(트레일링 슬래시 등)는 제외
|
|
19
|
+
const info = parseErrorMessages[error.code];
|
|
20
|
+
findings.push({
|
|
21
|
+
ruleId: rule.id,
|
|
22
|
+
engine: "k-rule",
|
|
23
|
+
kwcag: rule.kwcag,
|
|
24
|
+
wcag: rule.wcag,
|
|
25
|
+
impact: rule.impact,
|
|
26
|
+
outcome: "fail",
|
|
27
|
+
confidence: rule.confidence,
|
|
28
|
+
selector: `L${error.startLine}:C${error.startCol}`,
|
|
29
|
+
html: "",
|
|
30
|
+
message: info?.message ?? `마크업 오류: ${error.code}`,
|
|
31
|
+
fix: info?.fix ?? "마크업을 표준에 맞게 수정하세요.",
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
return findings;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=k-parse-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-parse-errors.js","sourceRoot":"","sources":["../../../src/rules/k/k-parse-errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,WAAW,IAAI,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAItE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAE3D,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,OAAO;IACf,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;YACd,sBAAsB,EAAE,IAAI;YAC5B,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE;gBACtB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;oBAAE,OAAO,CAAC,yBAAyB;gBACjE,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC5C,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,MAAM,EAAE,QAAQ;oBAChB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,OAAO,EAAE,MAAM;oBACf,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,QAAQ,EAAE,IAAI,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,QAAQ,EAAE;oBAClD,IAAI,EAAE,EAAE;oBACR,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,WAAW,KAAK,CAAC,IAAI,EAAE;oBACjD,GAAG,EAAE,IAAI,EAAE,GAAG,IAAI,oBAAoB;iBACvC,CAAC,CAAC;YACL,CAAC;SACF,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { placeholderOnlyLabel } from "../../messages.js";
|
|
4
|
+
export const rule = {
|
|
5
|
+
id: "k-placeholder-only-label",
|
|
6
|
+
kwcag: "7.3.2",
|
|
7
|
+
wcag: ["3.3.2"],
|
|
8
|
+
engine: "k",
|
|
9
|
+
impact: "critical",
|
|
10
|
+
confidence: "high",
|
|
11
|
+
tier: "T1",
|
|
12
|
+
run(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
const labels = Array.from(ctx.document.querySelectorAll("label"));
|
|
15
|
+
for (const input of Array.from(ctx.document.querySelectorAll("input[placeholder], textarea[placeholder]"))) {
|
|
16
|
+
const id = input.getAttribute("id");
|
|
17
|
+
const hasLabelFor = id !== null && labels.some((l) => l.getAttribute("for") === id);
|
|
18
|
+
const hasAriaLabel = normText(input.getAttribute("aria-label") ?? "") !== "";
|
|
19
|
+
const hasAriaLabelledby = (input.getAttribute("aria-labelledby") ?? "").trim() !== "";
|
|
20
|
+
const wrappedInLabel = input.closest("label") !== null;
|
|
21
|
+
if (hasLabelFor || hasAriaLabel || hasAriaLabelledby || wrappedInLabel)
|
|
22
|
+
continue;
|
|
23
|
+
findings.push(createFinding(rule, input, {
|
|
24
|
+
message: placeholderOnlyLabel.message,
|
|
25
|
+
fix: placeholderOnlyLabel.fix,
|
|
26
|
+
outcome: "fail",
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
return findings;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=k-placeholder-only-label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-placeholder-only-label.js","sourceRoot":"","sources":["../../../src/rules/k/k-placeholder-only-label.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAIzD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,0BAA0B;IAC9B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,MAAM;IAClB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;QAClE,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,2CAA2C,CAAC,CAAC,EAAE,CAAC;YAC3G,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM,WAAW,GAAG,EAAE,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YACpF,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YAC7E,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;YACtF,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;YACvD,IAAI,WAAW,IAAI,YAAY,IAAI,iBAAiB,IAAI,cAAc;gBAAE,SAAS;YACjF,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE;gBACzB,OAAO,EAAE,oBAAoB,CAAC,OAAO;gBACrC,GAAG,EAAE,oBAAoB,CAAC,GAAG;gBAC7B,OAAO,EAAE,MAAM;aAChB,CAAC,CACH,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { selectOnchange } from "../../messages.js";
|
|
3
|
+
export const rule = {
|
|
4
|
+
id: "k-select-onchange",
|
|
5
|
+
kwcag: "7.2.1",
|
|
6
|
+
wcag: ["3.2.1", "3.2.2"],
|
|
7
|
+
engine: "k",
|
|
8
|
+
impact: "moderate",
|
|
9
|
+
confidence: "low",
|
|
10
|
+
tier: "T1",
|
|
11
|
+
run(ctx) {
|
|
12
|
+
const findings = [];
|
|
13
|
+
for (const select of Array.from(ctx.document.querySelectorAll("select[onchange]"))) {
|
|
14
|
+
findings.push(createFinding(rule, select, { message: selectOnchange.message, fix: selectOnchange.fix, outcome: "incomplete" }));
|
|
15
|
+
}
|
|
16
|
+
return findings;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=k-select-onchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-select-onchange.js","sourceRoot":"","sources":["../../../src/rules/k/k-select-onchange.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAInD,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,UAAU;IAClB,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC;YACnF,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CACjH,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createFinding } from "../../normalize/finding.js";
|
|
2
|
+
import { normText } from "../util/text.js";
|
|
3
|
+
import { sensoryInstruction } from "../../messages.js";
|
|
4
|
+
// 감각적 특성에만 의존한 지시어 후보(위치·모양·색). 지시 맥락("누르|클릭|선택|참고|보세요")과 함께
|
|
5
|
+
// 나타날 때만 후보로 본다 — 오탐을 줄이기 위한 저신뢰 휴리스틱이다.
|
|
6
|
+
const SENSORY = ["오른쪽", "왼쪽", "위쪽", "아래쪽", "위의", "아래의", "빨간", "파란", "초록", "동그란", "네모난", "둥근"];
|
|
7
|
+
const ACTION = /누르|클릭|선택|참고|보세요|이동/;
|
|
8
|
+
export const rule = {
|
|
9
|
+
id: "k-sensory-instruction",
|
|
10
|
+
kwcag: "5.3.3",
|
|
11
|
+
wcag: ["1.3.3"],
|
|
12
|
+
engine: "k",
|
|
13
|
+
impact: "minor",
|
|
14
|
+
confidence: "low",
|
|
15
|
+
tier: "T2",
|
|
16
|
+
run(ctx) {
|
|
17
|
+
const findings = [];
|
|
18
|
+
for (const el of Array.from(ctx.document.querySelectorAll("p, li, label, span, div, a, button"))) {
|
|
19
|
+
if (el.children.length > 0)
|
|
20
|
+
continue; // 텍스트 리프 노드만(중복 방지)
|
|
21
|
+
const text = normText(el.textContent ?? "");
|
|
22
|
+
if (text === "" || !ACTION.test(text))
|
|
23
|
+
continue;
|
|
24
|
+
const hit = SENSORY.find((w) => text.includes(w));
|
|
25
|
+
if (hit) {
|
|
26
|
+
findings.push(createFinding(rule, el, { message: sensoryInstruction.message(hit), fix: sensoryInstruction.fix, outcome: "incomplete" }));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return findings;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=k-sensory-instruction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"k-sensory-instruction.js","sourceRoot":"","sources":["../../../src/rules/k/k-sensory-instruction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAIvD,6DAA6D;AAC7D,yCAAyC;AACzC,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AAC9F,MAAM,MAAM,GAAG,oBAAoB,CAAC;AAEpC,MAAM,CAAC,MAAM,IAAI,GAAe;IAC9B,EAAE,EAAE,uBAAuB;IAC3B,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,CAAC,OAAO,CAAC;IACf,MAAM,EAAE,GAAG;IACX,MAAM,EAAE,OAAO;IACf,UAAU,EAAE,KAAK;IACjB,IAAI,EAAE,IAAI;IACV,GAAG,CAAC,GAAkB;QACpB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oCAAoC,CAAC,CAAC,EAAE,CAAC;YACjG,IAAI,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS,CAAC,oBAAoB;YAC1D,MAAM,IAAI,GAAG,QAAQ,CAAC,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YAC5C,IAAI,IAAI,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YAClD,IAAI,GAAG,EAAE,CAAC;gBACR,QAAQ,CAAC,IAAI,CACX,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAC1H,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|