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.
Files changed (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/assets/alt-text-ko.json +7 -0
  4. package/assets/axe-rule-map.json +196 -0
  5. package/assets/certification.json +47 -0
  6. package/assets/kwcag22-checklist.md +123 -0
  7. package/assets/kwcag22.json +1688 -0
  8. package/assets/link-text-ko.json +8 -0
  9. package/assets/mobile-app-2.0.md +38 -0
  10. package/assets/sources.json +68 -0
  11. package/assets/wcag22.json +780 -0
  12. package/bin/swwa-mcp.mjs +15 -0
  13. package/dist/color/contrast.js +175 -0
  14. package/dist/color/contrast.js.map +1 -0
  15. package/dist/data/axe-map.js +32 -0
  16. package/dist/data/axe-map.js.map +1 -0
  17. package/dist/data/certification.js +54 -0
  18. package/dist/data/certification.js.map +1 -0
  19. package/dist/data/kwcag22.js +91 -0
  20. package/dist/data/kwcag22.js.map +1 -0
  21. package/dist/data/loader.js +18 -0
  22. package/dist/data/loader.js.map +1 -0
  23. package/dist/data/sources.js +44 -0
  24. package/dist/data/sources.js.map +1 -0
  25. package/dist/data/wcag22.js +44 -0
  26. package/dist/data/wcag22.js.map +1 -0
  27. package/dist/data/wordlists.js +40 -0
  28. package/dist/data/wordlists.js.map +1 -0
  29. package/dist/engine/browser-detect.js +44 -0
  30. package/dist/engine/browser-detect.js.map +1 -0
  31. package/dist/engine/browser.js +114 -0
  32. package/dist/engine/browser.js.map +1 -0
  33. package/dist/engine/static-worker.js +18 -0
  34. package/dist/engine/static-worker.js.map +1 -0
  35. package/dist/engine/static.js +139 -0
  36. package/dist/engine/static.js.map +1 -0
  37. package/dist/engine/url-guard.js +37 -0
  38. package/dist/engine/url-guard.js.map +1 -0
  39. package/dist/index.js +23 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/messages.js +176 -0
  42. package/dist/messages.js.map +1 -0
  43. package/dist/normalize/axe.js +72 -0
  44. package/dist/normalize/axe.js.map +1 -0
  45. package/dist/normalize/checkpoints.js +47 -0
  46. package/dist/normalize/checkpoints.js.map +1 -0
  47. package/dist/normalize/finding.js +24 -0
  48. package/dist/normalize/finding.js.map +1 -0
  49. package/dist/normalize/locale.js +20 -0
  50. package/dist/normalize/locale.js.map +1 -0
  51. package/dist/prompts/audit-report.js +45 -0
  52. package/dist/prompts/audit-report.js.map +1 -0
  53. package/dist/prompts/review-markup.js +35 -0
  54. package/dist/prompts/review-markup.js.map +1 -0
  55. package/dist/report/cert.js +72 -0
  56. package/dist/report/cert.js.map +1 -0
  57. package/dist/report/format.js +36 -0
  58. package/dist/report/format.js.map +1 -0
  59. package/dist/report/summarize.js +61 -0
  60. package/dist/report/summarize.js.map +1 -0
  61. package/dist/report/types.js +2 -0
  62. package/dist/report/types.js.map +1 -0
  63. package/dist/resources/index.js +69 -0
  64. package/dist/resources/index.js.map +1 -0
  65. package/dist/rules/b/b-focus-order.js +33 -0
  66. package/dist/rules/b/b-focus-order.js.map +1 -0
  67. package/dist/rules/b/b-focus-visible.js +71 -0
  68. package/dist/rules/b/b-focus-visible.js.map +1 -0
  69. package/dist/rules/b/b-keyboard-reachable.js +82 -0
  70. package/dist/rules/b/b-keyboard-reachable.js.map +1 -0
  71. package/dist/rules/b/b-motion-runtime.js +50 -0
  72. package/dist/rules/b/b-motion-runtime.js.map +1 -0
  73. package/dist/rules/b/b-skip-link-works.js +58 -0
  74. package/dist/rules/b/b-skip-link-works.js.map +1 -0
  75. package/dist/rules/b/b-target-size-6mm.js +54 -0
  76. package/dist/rules/b/b-target-size-6mm.js.map +1 -0
  77. package/dist/rules/b/index.js +7 -0
  78. package/dist/rules/b/index.js.map +1 -0
  79. package/dist/rules/k/index.js +33 -0
  80. package/dist/rules/k/index.js.map +1 -0
  81. package/dist/rules/k/k-accesskey.js +20 -0
  82. package/dist/rules/k/k-accesskey.js.map +1 -0
  83. package/dist/rules/k/k-alt-meaningless.js +35 -0
  84. package/dist/rules/k/k-alt-meaningless.js.map +1 -0
  85. package/dist/rules/k/k-autocomplete-missing.js +36 -0
  86. package/dist/rules/k/k-autocomplete-missing.js.map +1 -0
  87. package/dist/rules/k/k-autoplay-media.js +30 -0
  88. package/dist/rules/k/k-autoplay-media.js.map +1 -0
  89. package/dist/rules/k/k-captcha-detect.js +24 -0
  90. package/dist/rules/k/k-captcha-detect.js.map +1 -0
  91. package/dist/rules/k/k-carousel-no-pause.js +33 -0
  92. package/dist/rules/k/k-carousel-no-pause.js.map +1 -0
  93. package/dist/rules/k/k-device-motion.js +22 -0
  94. package/dist/rules/k/k-device-motion.js.map +1 -0
  95. package/dist/rules/k/k-down-event-action.js +19 -0
  96. package/dist/rules/k/k-down-event-action.js.map +1 -0
  97. package/dist/rules/k/k-error-association.js +31 -0
  98. package/dist/rules/k/k-error-association.js.map +1 -0
  99. package/dist/rules/k/k-flash-animation.js +57 -0
  100. package/dist/rules/k/k-flash-animation.js.map +1 -0
  101. package/dist/rules/k/k-gesture-listener.js +30 -0
  102. package/dist/rules/k/k-gesture-listener.js.map +1 -0
  103. package/dist/rules/k/k-iframe-title.js +23 -0
  104. package/dist/rules/k/k-iframe-title.js.map +1 -0
  105. package/dist/rules/k/k-lang-ko-expected.js +29 -0
  106. package/dist/rules/k/k-lang-ko-expected.js.map +1 -0
  107. package/dist/rules/k/k-link-same-text-diff-href.js +41 -0
  108. package/dist/rules/k/k-link-same-text-diff-href.js.map +1 -0
  109. package/dist/rules/k/k-link-text-generic.js +29 -0
  110. package/dist/rules/k/k-link-text-generic.js.map +1 -0
  111. package/dist/rules/k/k-media-track.js +22 -0
  112. package/dist/rules/k/k-media-track.js.map +1 -0
  113. package/dist/rules/k/k-mouse-only-handler.js +27 -0
  114. package/dist/rules/k/k-mouse-only-handler.js.map +1 -0
  115. package/dist/rules/k/k-new-window-notice.js +27 -0
  116. package/dist/rules/k/k-new-window-notice.js.map +1 -0
  117. package/dist/rules/k/k-onload-popup.js +30 -0
  118. package/dist/rules/k/k-onload-popup.js.map +1 -0
  119. package/dist/rules/k/k-outline-none.js +55 -0
  120. package/dist/rules/k/k-outline-none.js.map +1 -0
  121. package/dist/rules/k/k-parse-errors.js +38 -0
  122. package/dist/rules/k/k-parse-errors.js.map +1 -0
  123. package/dist/rules/k/k-placeholder-only-label.js +32 -0
  124. package/dist/rules/k/k-placeholder-only-label.js.map +1 -0
  125. package/dist/rules/k/k-select-onchange.js +19 -0
  126. package/dist/rules/k/k-select-onchange.js.map +1 -0
  127. package/dist/rules/k/k-sensory-instruction.js +32 -0
  128. package/dist/rules/k/k-sensory-instruction.js.map +1 -0
  129. package/dist/rules/k/k-session-timeout-hint.js +29 -0
  130. package/dist/rules/k/k-session-timeout-hint.js.map +1 -0
  131. package/dist/rules/k/k-skip-link-first.js +23 -0
  132. package/dist/rules/k/k-skip-link-first.js.map +1 -0
  133. package/dist/rules/k/k-skip-target-exists.js +28 -0
  134. package/dist/rules/k/k-skip-target-exists.js.map +1 -0
  135. package/dist/rules/k/k-tabindex-positive.js +23 -0
  136. package/dist/rules/k/k-tabindex-positive.js.map +1 -0
  137. package/dist/rules/k/k-table-caption.js +26 -0
  138. package/dist/rules/k/k-table-caption.js.map +1 -0
  139. package/dist/rules/k/k-table-th-missing.js +28 -0
  140. package/dist/rules/k/k-table-th-missing.js.map +1 -0
  141. package/dist/rules/k/k-title-generic.js +35 -0
  142. package/dist/rules/k/k-title-generic.js.map +1 -0
  143. package/dist/rules/registry.js +62 -0
  144. package/dist/rules/registry.js.map +1 -0
  145. package/dist/rules/types.js +2 -0
  146. package/dist/rules/types.js.map +1 -0
  147. package/dist/rules/util/browser-dom.js +50 -0
  148. package/dist/rules/util/browser-dom.js.map +1 -0
  149. package/dist/rules/util/focusable.js +29 -0
  150. package/dist/rules/util/focusable.js.map +1 -0
  151. package/dist/rules/util/selector.js +19 -0
  152. package/dist/rules/util/selector.js.map +1 -0
  153. package/dist/rules/util/text.js +13 -0
  154. package/dist/rules/util/text.js.map +1 -0
  155. package/dist/schema-dialect.js +41 -0
  156. package/dist/schema-dialect.js.map +1 -0
  157. package/dist/server.js +36 -0
  158. package/dist/server.js.map +1 -0
  159. package/dist/tools/audit-url.js +109 -0
  160. package/dist/tools/audit-url.js.map +1 -0
  161. package/dist/tools/browser-status.js +29 -0
  162. package/dist/tools/browser-status.js.map +1 -0
  163. package/dist/tools/cert-readiness.js +76 -0
  164. package/dist/tools/cert-readiness.js.map +1 -0
  165. package/dist/tools/check-contrast.js +58 -0
  166. package/dist/tools/check-contrast.js.map +1 -0
  167. package/dist/tools/check-html.js +88 -0
  168. package/dist/tools/check-html.js.map +1 -0
  169. package/dist/tools/checklist.js +94 -0
  170. package/dist/tools/checklist.js.map +1 -0
  171. package/dist/tools/lookup.js +70 -0
  172. package/dist/tools/lookup.js.map +1 -0
  173. package/dist/tools/report-schema.js +49 -0
  174. package/dist/tools/report-schema.js.map +1 -0
  175. 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"}