@arcaauth/eslint-plugin-jsx-a11y 6.10.2

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 (229) hide show
  1. package/.babelrc +17 -0
  2. package/.eslintrc +44 -0
  3. package/CHANGELOG.md +774 -0
  4. package/LICENSE.md +8 -0
  5. package/README.md +423 -0
  6. package/__mocks__/IdentifierMock.js +15 -0
  7. package/__mocks__/JSXAttributeMock.js +39 -0
  8. package/__mocks__/JSXElementMock.js +37 -0
  9. package/__mocks__/JSXExpressionContainerMock.js +15 -0
  10. package/__mocks__/JSXSpreadAttributeMock.js +18 -0
  11. package/__mocks__/JSXTextMock.js +17 -0
  12. package/__mocks__/LiteralMock.js +17 -0
  13. package/__mocks__/genInteractives.js +218 -0
  14. package/__tests__/__util__/axeMapping.js +6 -0
  15. package/__tests__/__util__/helpers/getESLintCoreRule.js +9 -0
  16. package/__tests__/__util__/helpers/parsers.js +186 -0
  17. package/__tests__/__util__/parserOptionsMapper.js +53 -0
  18. package/__tests__/__util__/ruleOptionsMapperFactory.js +33 -0
  19. package/__tests__/index-test.js +40 -0
  20. package/__tests__/src/rules/accessible-emoji-test.js +66 -0
  21. package/__tests__/src/rules/alt-text-test.js +291 -0
  22. package/__tests__/src/rules/anchor-ambiguous-text-test.js +117 -0
  23. package/__tests__/src/rules/anchor-has-content-test.js +54 -0
  24. package/__tests__/src/rules/anchor-is-valid-test.js +532 -0
  25. package/__tests__/src/rules/aria-activedescendant-has-tabindex-test.js +95 -0
  26. package/__tests__/src/rules/aria-props-test.js +69 -0
  27. package/__tests__/src/rules/aria-proptypes-test.js +311 -0
  28. package/__tests__/src/rules/aria-role-test.js +118 -0
  29. package/__tests__/src/rules/aria-unsupported-elements-test.js +75 -0
  30. package/__tests__/src/rules/autocomplete-valid-test.js +77 -0
  31. package/__tests__/src/rules/click-events-have-key-events-test.js +77 -0
  32. package/__tests__/src/rules/control-has-associated-label-test.js +327 -0
  33. package/__tests__/src/rules/heading-has-content-test.js +85 -0
  34. package/__tests__/src/rules/html-has-lang-test.js +42 -0
  35. package/__tests__/src/rules/iframe-has-title-test.js +55 -0
  36. package/__tests__/src/rules/img-redundant-alt-test.js +137 -0
  37. package/__tests__/src/rules/interactive-supports-focus-test.js +267 -0
  38. package/__tests__/src/rules/label-has-associated-control-test.js +243 -0
  39. package/__tests__/src/rules/label-has-for-test.js +235 -0
  40. package/__tests__/src/rules/lang-test.js +59 -0
  41. package/__tests__/src/rules/media-has-caption-test.js +220 -0
  42. package/__tests__/src/rules/mouse-events-have-key-events-test.js +154 -0
  43. package/__tests__/src/rules/no-access-key-test.js +48 -0
  44. package/__tests__/src/rules/no-aria-hidden-on-focusable-test.js +44 -0
  45. package/__tests__/src/rules/no-autofocus-test.js +68 -0
  46. package/__tests__/src/rules/no-distracting-elements-test.js +51 -0
  47. package/__tests__/src/rules/no-interactive-element-to-noninteractive-role-test.js +405 -0
  48. package/__tests__/src/rules/no-noninteractive-element-interactions-test.js +502 -0
  49. package/__tests__/src/rules/no-noninteractive-element-to-interactive-role-test.js +500 -0
  50. package/__tests__/src/rules/no-noninteractive-tabindex-test.js +123 -0
  51. package/__tests__/src/rules/no-onchange-test.js +57 -0
  52. package/__tests__/src/rules/no-redundant-roles-test.js +98 -0
  53. package/__tests__/src/rules/no-static-element-interactions-test.js +501 -0
  54. package/__tests__/src/rules/prefer-tag-over-role-test.js +63 -0
  55. package/__tests__/src/rules/role-has-required-aria-props-test.js +134 -0
  56. package/__tests__/src/rules/role-supports-aria-props-test.js +570 -0
  57. package/__tests__/src/rules/scope-test.js +50 -0
  58. package/__tests__/src/rules/tabindex-no-positive-test.js +55 -0
  59. package/__tests__/src/util/attributesComparator-test.js +91 -0
  60. package/__tests__/src/util/getAccessibleChildText-test.js +174 -0
  61. package/__tests__/src/util/getComputedRole-test.js +71 -0
  62. package/__tests__/src/util/getElementType-test.js +154 -0
  63. package/__tests__/src/util/getExplicitRole-test.js +35 -0
  64. package/__tests__/src/util/getImplicitRole-test.js +25 -0
  65. package/__tests__/src/util/getSuggestion-test.js +33 -0
  66. package/__tests__/src/util/getTabIndex-test.js +85 -0
  67. package/__tests__/src/util/hasAccessibleChild-test.js +157 -0
  68. package/__tests__/src/util/implicitRoles/input-test.js +87 -0
  69. package/__tests__/src/util/implicitRoles/menu-test.js +20 -0
  70. package/__tests__/src/util/implicitRoles/menuitem-test.js +38 -0
  71. package/__tests__/src/util/isAbstractRole-test.js +51 -0
  72. package/__tests__/src/util/isContentEditable-test.js +52 -0
  73. package/__tests__/src/util/isDOMElement-test.js +30 -0
  74. package/__tests__/src/util/isDisabledElement-test.js +88 -0
  75. package/__tests__/src/util/isFocusable-test.js +111 -0
  76. package/__tests__/src/util/isInteractiveElement-test.js +104 -0
  77. package/__tests__/src/util/isInteractiveRole-test.js +59 -0
  78. package/__tests__/src/util/isNonInteractiveElement-test.js +97 -0
  79. package/__tests__/src/util/isNonInteractiveRole-test.js +59 -0
  80. package/__tests__/src/util/isNonLiteralProperty-test.js +52 -0
  81. package/__tests__/src/util/isSemanticRoleElement-test.js +72 -0
  82. package/__tests__/src/util/mayContainChildComponent-test.js +219 -0
  83. package/__tests__/src/util/mayHaveAccessibleLabel-test.js +256 -0
  84. package/__tests__/src/util/parserOptionsMapper-test.js +93 -0
  85. package/__tests__/src/util/schemas-test.js +35 -0
  86. package/docs/rules/accessible-emoji.md +30 -0
  87. package/docs/rules/alt-text.md +168 -0
  88. package/docs/rules/anchor-ambiguous-text.md +91 -0
  89. package/docs/rules/anchor-has-content.md +64 -0
  90. package/docs/rules/anchor-is-valid.md +270 -0
  91. package/docs/rules/aria-activedescendant-has-tabindex.md +52 -0
  92. package/docs/rules/aria-props.md +29 -0
  93. package/docs/rules/aria-proptypes.md +30 -0
  94. package/docs/rules/aria-role.md +51 -0
  95. package/docs/rules/aria-unsupported-elements.md +30 -0
  96. package/docs/rules/autocomplete-valid.md +49 -0
  97. package/docs/rules/click-events-have-key-events.md +28 -0
  98. package/docs/rules/control-has-associated-label.md +113 -0
  99. package/docs/rules/heading-has-content.md +67 -0
  100. package/docs/rules/html-has-lang.md +31 -0
  101. package/docs/rules/iframe-has-title.md +37 -0
  102. package/docs/rules/img-redundant-alt.md +48 -0
  103. package/docs/rules/interactive-supports-focus.md +156 -0
  104. package/docs/rules/label-has-associated-control.md +152 -0
  105. package/docs/rules/label-has-for.md +130 -0
  106. package/docs/rules/lang.md +31 -0
  107. package/docs/rules/media-has-caption.md +48 -0
  108. package/docs/rules/mouse-events-have-key-events.md +58 -0
  109. package/docs/rules/no-access-key.md +30 -0
  110. package/docs/rules/no-aria-hidden-on-focusable.md +37 -0
  111. package/docs/rules/no-autofocus.md +43 -0
  112. package/docs/rules/no-distracting-elements.md +41 -0
  113. package/docs/rules/no-interactive-element-to-noninteractive-role.md +73 -0
  114. package/docs/rules/no-noninteractive-element-interactions.md +145 -0
  115. package/docs/rules/no-noninteractive-element-to-interactive-role.md +76 -0
  116. package/docs/rules/no-noninteractive-tabindex.md +115 -0
  117. package/docs/rules/no-onchange.md +36 -0
  118. package/docs/rules/no-redundant-roles.md +46 -0
  119. package/docs/rules/no-static-element-interactions.md +114 -0
  120. package/docs/rules/prefer-tag-over-role.md +32 -0
  121. package/docs/rules/role-has-required-aria-props.md +31 -0
  122. package/docs/rules/role-supports-aria-props.md +39 -0
  123. package/docs/rules/scope.md +30 -0
  124. package/docs/rules/tabindex-no-positive.md +32 -0
  125. package/lib/configs/flat-config-base.js +11 -0
  126. package/lib/configs/legacy-config-base.js +9 -0
  127. package/lib/index.js +209 -0
  128. package/lib/rules/accessible-emoji.js +63 -0
  129. package/lib/rules/alt-text.js +218 -0
  130. package/lib/rules/anchor-ambiguous-text.js +64 -0
  131. package/lib/rules/anchor-has-content.js +60 -0
  132. package/lib/rules/anchor-is-valid.js +122 -0
  133. package/lib/rules/aria-activedescendant-has-tabindex.js +66 -0
  134. package/lib/rules/aria-props.js +59 -0
  135. package/lib/rules/aria-proptypes.js +114 -0
  136. package/lib/rules/aria-role.js +89 -0
  137. package/lib/rules/aria-unsupported-elements.js +64 -0
  138. package/lib/rules/autocomplete-valid.js +67 -0
  139. package/lib/rules/click-events-have-key-events.js +68 -0
  140. package/lib/rules/control-has-associated-label.js +103 -0
  141. package/lib/rules/heading-has-content.js +61 -0
  142. package/lib/rules/html-has-lang.js +50 -0
  143. package/lib/rules/iframe-has-title.js +50 -0
  144. package/lib/rules/img-redundant-alt.js +88 -0
  145. package/lib/rules/interactive-supports-focus.js +87 -0
  146. package/lib/rules/label-has-associated-control.js +127 -0
  147. package/lib/rules/label-has-for.js +150 -0
  148. package/lib/rules/lang.js +68 -0
  149. package/lib/rules/media-has-caption.js +96 -0
  150. package/lib/rules/mouse-events-have-key-events.js +94 -0
  151. package/lib/rules/no-access-key.js +43 -0
  152. package/lib/rules/no-aria-hidden-on-focusable.js +47 -0
  153. package/lib/rules/no-autofocus.js +62 -0
  154. package/lib/rules/no-distracting-elements.js +54 -0
  155. package/lib/rules/no-interactive-element-to-noninteractive-role.js +81 -0
  156. package/lib/rules/no-noninteractive-element-interactions.js +95 -0
  157. package/lib/rules/no-noninteractive-element-to-interactive-role.js +80 -0
  158. package/lib/rules/no-noninteractive-tabindex.js +109 -0
  159. package/lib/rules/no-onchange.js +52 -0
  160. package/lib/rules/no-redundant-roles.js +86 -0
  161. package/lib/rules/no-static-element-interactions.js +102 -0
  162. package/lib/rules/prefer-tag-over-role.js +75 -0
  163. package/lib/rules/role-has-required-aria-props.js +88 -0
  164. package/lib/rules/role-supports-aria-props.js +78 -0
  165. package/lib/rules/scope.js +58 -0
  166. package/lib/rules/tabindex-no-positive.js +53 -0
  167. package/lib/util/attributesComparator.js +34 -0
  168. package/lib/util/getAccessibleChildText.js +55 -0
  169. package/lib/util/getComputedRole.js +19 -0
  170. package/lib/util/getElementType.js +30 -0
  171. package/lib/util/getExplicitRole.js +27 -0
  172. package/lib/util/getImplicitRole.js +24 -0
  173. package/lib/util/getSuggestion.js +32 -0
  174. package/lib/util/getTabIndex.js +34 -0
  175. package/lib/util/hasAccessibleChild.js +30 -0
  176. package/lib/util/implicitRoles/a.js +17 -0
  177. package/lib/util/implicitRoles/area.js +17 -0
  178. package/lib/util/implicitRoles/article.js +13 -0
  179. package/lib/util/implicitRoles/aside.js +13 -0
  180. package/lib/util/implicitRoles/body.js +13 -0
  181. package/lib/util/implicitRoles/button.js +13 -0
  182. package/lib/util/implicitRoles/datalist.js +13 -0
  183. package/lib/util/implicitRoles/details.js +13 -0
  184. package/lib/util/implicitRoles/dialog.js +13 -0
  185. package/lib/util/implicitRoles/form.js +13 -0
  186. package/lib/util/implicitRoles/h1.js +13 -0
  187. package/lib/util/implicitRoles/h2.js +13 -0
  188. package/lib/util/implicitRoles/h3.js +13 -0
  189. package/lib/util/implicitRoles/h4.js +13 -0
  190. package/lib/util/implicitRoles/h5.js +13 -0
  191. package/lib/util/implicitRoles/h6.js +13 -0
  192. package/lib/util/implicitRoles/hr.js +13 -0
  193. package/lib/util/implicitRoles/img.js +31 -0
  194. package/lib/util/implicitRoles/index.js +82 -0
  195. package/lib/util/implicitRoles/input.js +38 -0
  196. package/lib/util/implicitRoles/li.js +13 -0
  197. package/lib/util/implicitRoles/link.js +17 -0
  198. package/lib/util/implicitRoles/menu.js +19 -0
  199. package/lib/util/implicitRoles/menuitem.js +28 -0
  200. package/lib/util/implicitRoles/meter.js +13 -0
  201. package/lib/util/implicitRoles/nav.js +13 -0
  202. package/lib/util/implicitRoles/ol.js +13 -0
  203. package/lib/util/implicitRoles/option.js +13 -0
  204. package/lib/util/implicitRoles/output.js +13 -0
  205. package/lib/util/implicitRoles/progress.js +13 -0
  206. package/lib/util/implicitRoles/section.js +13 -0
  207. package/lib/util/implicitRoles/select.js +13 -0
  208. package/lib/util/implicitRoles/tbody.js +13 -0
  209. package/lib/util/implicitRoles/textarea.js +13 -0
  210. package/lib/util/implicitRoles/tfoot.js +13 -0
  211. package/lib/util/implicitRoles/thead.js +13 -0
  212. package/lib/util/implicitRoles/ul.js +13 -0
  213. package/lib/util/isAbstractRole.js +23 -0
  214. package/lib/util/isContentEditable.js +13 -0
  215. package/lib/util/isDOMElement.js +15 -0
  216. package/lib/util/isDisabledElement.js +23 -0
  217. package/lib/util/isFocusable.js +23 -0
  218. package/lib/util/isHiddenFromScreenReader.js +26 -0
  219. package/lib/util/isInteractiveElement.js +116 -0
  220. package/lib/util/isInteractiveRole.js +54 -0
  221. package/lib/util/isNonInteractiveElement.js +131 -0
  222. package/lib/util/isNonInteractiveRole.js +55 -0
  223. package/lib/util/isNonLiteralProperty.js +29 -0
  224. package/lib/util/isPresentationRole.js +13 -0
  225. package/lib/util/isSemanticRoleElement.js +54 -0
  226. package/lib/util/mayContainChildComponent.js +50 -0
  227. package/lib/util/mayHaveAccessibleLabel.js +95 -0
  228. package/lib/util/schemas.js +52 -0
  229. package/package.json +120 -0
@@ -0,0 +1,570 @@
1
+ /**
2
+ * @fileoverview Enforce that an element does not have an unsupported ARIA attribute.
3
+ * @author Ethan Cohen
4
+ */
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Requirements
8
+ // -----------------------------------------------------------------------------
9
+
10
+ import {
11
+ aria,
12
+ roles,
13
+ } from 'aria-query';
14
+ import { RuleTester } from 'eslint';
15
+ import { version as eslintVersion } from 'eslint/package.json';
16
+ import semver from 'semver';
17
+
18
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
19
+ import parsers from '../../__util__/helpers/parsers';
20
+ import rule from '../../../src/rules/role-supports-aria-props';
21
+
22
+ // -----------------------------------------------------------------------------
23
+ // Tests
24
+ // -----------------------------------------------------------------------------
25
+
26
+ const ruleTester = new RuleTester();
27
+
28
+ const generateErrorMessage = (attr, role, tag, isImplicit) => {
29
+ if (isImplicit) {
30
+ return `The attribute ${attr} is not supported by the role ${role}. This role is implicit on the element ${tag}.`;
31
+ }
32
+
33
+ return `The attribute ${attr} is not supported by the role ${role}.`;
34
+ };
35
+
36
+ const errorMessage = (attr, role, tag, isImplicit) => ({
37
+ message: generateErrorMessage(attr, role, tag, isImplicit),
38
+ type: 'JSXOpeningElement',
39
+ });
40
+
41
+ const componentsSettings = {
42
+ 'jsx-a11y': {
43
+ components: {
44
+ Link: 'a',
45
+ },
46
+ },
47
+ };
48
+
49
+ const nonAbstractRoles = roles.keys().filter((role) => roles.get(role).abstract === false);
50
+
51
+ const createTests = (rolesNames) => rolesNames.reduce((tests, role) => {
52
+ const {
53
+ props: propKeyValues,
54
+ } = roles.get(role);
55
+ const validPropsForRole = Object.keys(propKeyValues);
56
+ const invalidPropsForRole = aria.keys()
57
+ .map((attribute) => attribute.toLowerCase())
58
+ .filter((attribute) => validPropsForRole.indexOf(attribute) === -1);
59
+ const normalRole = role.toLowerCase();
60
+
61
+ return [
62
+ tests[0].concat(validPropsForRole.map((prop) => ({
63
+ code: `<div role="${normalRole}" ${prop.toLowerCase()} />`,
64
+ }))),
65
+ tests[1].concat(invalidPropsForRole.map((prop) => ({
66
+ code: `<div role="${normalRole}" ${prop.toLowerCase()} />`,
67
+ errors: [errorMessage(prop.toLowerCase(), normalRole, 'div', false)],
68
+ }))),
69
+ ];
70
+ }, [[], []]);
71
+
72
+ const [validTests, invalidTests] = createTests(nonAbstractRoles);
73
+
74
+ ruleTester.run('role-supports-aria-props', rule, {
75
+ valid: parsers.all([].concat(
76
+ { code: '<Foo bar />' },
77
+ { code: '<div />' },
78
+ { code: '<div id="main" />' },
79
+ { code: '<div role />' },
80
+ { code: '<div role="presentation" {...props} />' },
81
+ { code: '<Foo.Bar baz={true} />' },
82
+ { code: '<Link href="#" aria-checked />' },
83
+
84
+ // IMPLICIT ROLE TESTS
85
+ // A TESTS - implicit role is `link`
86
+ { code: '<a href="#" aria-expanded />' },
87
+ { code: '<a href="#" aria-atomic />' },
88
+ { code: '<a href="#" aria-busy />' },
89
+ { code: '<a href="#" aria-controls />' },
90
+ { code: '<a href="#" aria-current />' },
91
+ { code: '<a href="#" aria-describedby />' },
92
+ { code: '<a href="#" aria-disabled />' },
93
+ { code: '<a href="#" aria-dropeffect />' },
94
+ { code: '<a href="#" aria-flowto />' },
95
+ { code: '<a href="#" aria-haspopup />' },
96
+ { code: '<a href="#" aria-grabbed />' },
97
+ { code: '<a href="#" aria-hidden />' },
98
+ { code: '<a href="#" aria-label />' },
99
+ { code: '<a href="#" aria-labelledby />' },
100
+ { code: '<a href="#" aria-live />' },
101
+ { code: '<a href="#" aria-owns />' },
102
+ { code: '<a href="#" aria-relevant />' },
103
+
104
+ // this will have global
105
+ { code: '<a aria-checked />' },
106
+
107
+ // AREA TESTS - implicit role is `link`
108
+ { code: '<area href="#" aria-expanded />' },
109
+ { code: '<area href="#" aria-atomic />' },
110
+ { code: '<area href="#" aria-busy />' },
111
+ { code: '<area href="#" aria-controls />' },
112
+ { code: '<area href="#" aria-describedby />' },
113
+ { code: '<area href="#" aria-disabled />' },
114
+ { code: '<area href="#" aria-dropeffect />' },
115
+ { code: '<area href="#" aria-flowto />' },
116
+ { code: '<area href="#" aria-grabbed />' },
117
+ { code: '<area href="#" aria-haspopup />' },
118
+ { code: '<area href="#" aria-hidden />' },
119
+ { code: '<area href="#" aria-label />' },
120
+ { code: '<area href="#" aria-labelledby />' },
121
+ { code: '<area href="#" aria-live />' },
122
+ { code: '<area href="#" aria-owns />' },
123
+ { code: '<area href="#" aria-relevant />' },
124
+
125
+ // this will have global
126
+ { code: '<area aria-checked />' },
127
+
128
+ // LINK TESTS - implicit role is `link`
129
+ { code: '<link href="#" aria-expanded />' },
130
+ { code: '<link href="#" aria-atomic />' },
131
+ { code: '<link href="#" aria-busy />' },
132
+ { code: '<link href="#" aria-controls />' },
133
+ { code: '<link href="#" aria-describedby />' },
134
+ { code: '<link href="#" aria-disabled />' },
135
+ { code: '<link href="#" aria-dropeffect />' },
136
+ { code: '<link href="#" aria-flowto />' },
137
+ { code: '<link href="#" aria-grabbed />' },
138
+ { code: '<link href="#" aria-hidden />' },
139
+ { code: '<link href="#" aria-haspopup />' },
140
+ { code: '<link href="#" aria-label />' },
141
+ { code: '<link href="#" aria-labelledby />' },
142
+ { code: '<link href="#" aria-live />' },
143
+ { code: '<link href="#" aria-owns />' },
144
+ { code: '<link href="#" aria-relevant />' },
145
+
146
+ // this will have global
147
+ { code: '<link aria-checked />' },
148
+
149
+ // IMG TESTS - no implicit role
150
+ { code: '<img alt="" aria-checked />' },
151
+
152
+ // this will have role of `img`
153
+ { code: '<img alt="foobar" aria-busy />' },
154
+
155
+ // MENU TESTS - implicit role is `toolbar` when `type="toolbar"`
156
+ { code: '<menu type="toolbar" aria-activedescendant />' },
157
+ { code: '<menu type="toolbar" aria-atomic />' },
158
+ { code: '<menu type="toolbar" aria-busy />' },
159
+ { code: '<menu type="toolbar" aria-controls />' },
160
+ { code: '<menu type="toolbar" aria-describedby />' },
161
+ { code: '<menu type="toolbar" aria-disabled />' },
162
+ { code: '<menu type="toolbar" aria-dropeffect />' },
163
+ { code: '<menu type="toolbar" aria-flowto />' },
164
+ { code: '<menu type="toolbar" aria-grabbed />' },
165
+ { code: '<menu type="toolbar" aria-hidden />' },
166
+ { code: '<menu type="toolbar" aria-label />' },
167
+ { code: '<menu type="toolbar" aria-labelledby />' },
168
+ { code: '<menu type="toolbar" aria-live />' },
169
+ { code: '<menu type="toolbar" aria-owns />' },
170
+ { code: '<menu type="toolbar" aria-relevant />' },
171
+
172
+ // this will have global
173
+ { code: '<menu aria-checked />' },
174
+
175
+ // MENUITEM TESTS
176
+ // when `type="command`, the implicit role is `menuitem`
177
+ { code: '<menuitem type="command" aria-atomic />' },
178
+ { code: '<menuitem type="command" aria-busy />' },
179
+ { code: '<menuitem type="command" aria-controls />' },
180
+ { code: '<menuitem type="command" aria-describedby />' },
181
+ { code: '<menuitem type="command" aria-disabled />' },
182
+ { code: '<menuitem type="command" aria-dropeffect />' },
183
+ { code: '<menuitem type="command" aria-flowto />' },
184
+ { code: '<menuitem type="command" aria-grabbed />' },
185
+ { code: '<menuitem type="command" aria-haspopup />' },
186
+ { code: '<menuitem type="command" aria-hidden />' },
187
+ { code: '<menuitem type="command" aria-label />' },
188
+ { code: '<menuitem type="command" aria-labelledby />' },
189
+ { code: '<menuitem type="command" aria-live />' },
190
+ { code: '<menuitem type="command" aria-owns />' },
191
+ { code: '<menuitem type="command" aria-relevant />' },
192
+ // when `type="checkbox`, the implicit role is `menuitemcheckbox`
193
+ { code: '<menuitem type="checkbox" aria-checked />' },
194
+ { code: '<menuitem type="checkbox" aria-atomic />' },
195
+ { code: '<menuitem type="checkbox" aria-busy />' },
196
+ { code: '<menuitem type="checkbox" aria-controls />' },
197
+ { code: '<menuitem type="checkbox" aria-describedby />' },
198
+ { code: '<menuitem type="checkbox" aria-disabled />' },
199
+ { code: '<menuitem type="checkbox" aria-dropeffect />' },
200
+ { code: '<menuitem type="checkbox" aria-flowto />' },
201
+ { code: '<menuitem type="checkbox" aria-grabbed />' },
202
+ { code: '<menuitem type="checkbox" aria-haspopup />' },
203
+ { code: '<menuitem type="checkbox" aria-hidden />' },
204
+ { code: '<menuitem type="checkbox" aria-invalid />' },
205
+ { code: '<menuitem type="checkbox" aria-label />' },
206
+ { code: '<menuitem type="checkbox" aria-labelledby />' },
207
+ { code: '<menuitem type="checkbox" aria-live />' },
208
+ { code: '<menuitem type="checkbox" aria-owns />' },
209
+ { code: '<menuitem type="checkbox" aria-relevant />' },
210
+ // when `type="radio`, the implicit role is `menuitemradio`
211
+ { code: '<menuitem type="radio" aria-checked />' },
212
+ { code: '<menuitem type="radio" aria-atomic />' },
213
+ { code: '<menuitem type="radio" aria-busy />' },
214
+ { code: '<menuitem type="radio" aria-controls />' },
215
+ { code: '<menuitem type="radio" aria-describedby />' },
216
+ { code: '<menuitem type="radio" aria-disabled />' },
217
+ { code: '<menuitem type="radio" aria-dropeffect />' },
218
+ { code: '<menuitem type="radio" aria-flowto />' },
219
+ { code: '<menuitem type="radio" aria-grabbed />' },
220
+ { code: '<menuitem type="radio" aria-haspopup />' },
221
+ { code: '<menuitem type="radio" aria-hidden />' },
222
+ { code: '<menuitem type="radio" aria-invalid />' },
223
+ { code: '<menuitem type="radio" aria-label />' },
224
+ { code: '<menuitem type="radio" aria-labelledby />' },
225
+ { code: '<menuitem type="radio" aria-live />' },
226
+ { code: '<menuitem type="radio" aria-owns />' },
227
+ { code: '<menuitem type="radio" aria-relevant />' },
228
+ { code: '<menuitem type="radio" aria-posinset />' },
229
+ { code: '<menuitem type="radio" aria-setsize />' },
230
+
231
+ // these will have global
232
+ { code: '<menuitem aria-checked />' },
233
+ { code: '<menuitem type="foo" aria-checked />' },
234
+
235
+ // INPUT TESTS
236
+ // when `type="button"`, the implicit role is `button`
237
+ { code: '<input type="button" aria-expanded />' },
238
+ { code: '<input type="button" aria-pressed />' },
239
+ { code: '<input type="button" aria-atomic />' },
240
+ { code: '<input type="button" aria-busy />' },
241
+ { code: '<input type="button" aria-controls />' },
242
+ { code: '<input type="button" aria-describedby />' },
243
+ { code: '<input type="button" aria-disabled />' },
244
+ { code: '<input type="button" aria-dropeffect />' },
245
+ { code: '<input type="button" aria-flowto />' },
246
+ { code: '<input type="button" aria-grabbed />' },
247
+ { code: '<input type="button" aria-haspopup />' },
248
+ { code: '<input type="button" aria-hidden />' },
249
+ { code: '<input type="button" aria-label />' },
250
+ { code: '<input type="button" aria-labelledby />' },
251
+ { code: '<input type="button" aria-live />' },
252
+ { code: '<input type="button" aria-owns />' },
253
+ { code: '<input type="button" aria-relevant />' },
254
+ // when `type="image"`, the implicit role is `button`
255
+ { code: '<input type="image" aria-expanded />' },
256
+ { code: '<input type="image" aria-pressed />' },
257
+ { code: '<input type="image" aria-atomic />' },
258
+ { code: '<input type="image" aria-busy />' },
259
+ { code: '<input type="image" aria-controls />' },
260
+ { code: '<input type="image" aria-describedby />' },
261
+ { code: '<input type="image" aria-disabled />' },
262
+ { code: '<input type="image" aria-dropeffect />' },
263
+ { code: '<input type="image" aria-flowto />' },
264
+ { code: '<input type="image" aria-grabbed />' },
265
+ { code: '<input type="image" aria-haspopup />' },
266
+ { code: '<input type="image" aria-hidden />' },
267
+ { code: '<input type="image" aria-label />' },
268
+ { code: '<input type="image" aria-labelledby />' },
269
+ { code: '<input type="image" aria-live />' },
270
+ { code: '<input type="image" aria-owns />' },
271
+ { code: '<input type="image" aria-relevant />' },
272
+ // when `type="reset"`, the implicit role is `button`
273
+ { code: '<input type="reset" aria-expanded />' },
274
+ { code: '<input type="reset" aria-pressed />' },
275
+ { code: '<input type="reset" aria-atomic />' },
276
+ { code: '<input type="reset" aria-busy />' },
277
+ { code: '<input type="reset" aria-controls />' },
278
+ { code: '<input type="reset" aria-describedby />' },
279
+ { code: '<input type="reset" aria-disabled />' },
280
+ { code: '<input type="reset" aria-dropeffect />' },
281
+ { code: '<input type="reset" aria-flowto />' },
282
+ { code: '<input type="reset" aria-grabbed />' },
283
+ { code: '<input type="reset" aria-haspopup />' },
284
+ { code: '<input type="reset" aria-hidden />' },
285
+ { code: '<input type="reset" aria-label />' },
286
+ { code: '<input type="reset" aria-labelledby />' },
287
+ { code: '<input type="reset" aria-live />' },
288
+ { code: '<input type="reset" aria-owns />' },
289
+ { code: '<input type="reset" aria-relevant />' },
290
+ // when `type="submit"`, the implicit role is `button`
291
+ { code: '<input type="submit" aria-expanded />' },
292
+ { code: '<input type="submit" aria-pressed />' },
293
+ { code: '<input type="submit" aria-atomic />' },
294
+ { code: '<input type="submit" aria-busy />' },
295
+ { code: '<input type="submit" aria-controls />' },
296
+ { code: '<input type="submit" aria-describedby />' },
297
+ { code: '<input type="submit" aria-disabled />' },
298
+ { code: '<input type="submit" aria-dropeffect />' },
299
+ { code: '<input type="submit" aria-flowto />' },
300
+ { code: '<input type="submit" aria-grabbed />' },
301
+ { code: '<input type="submit" aria-haspopup />' },
302
+ { code: '<input type="submit" aria-hidden />' },
303
+ { code: '<input type="submit" aria-label />' },
304
+ { code: '<input type="submit" aria-labelledby />' },
305
+ { code: '<input type="submit" aria-live />' },
306
+ { code: '<input type="submit" aria-owns />' },
307
+ { code: '<input type="submit" aria-relevant />' },
308
+ // when `type="checkbox"`, the implicit role is `checkbox`
309
+ { code: '<input type="checkbox" aria-atomic />' },
310
+ { code: '<input type="checkbox" aria-busy />' },
311
+ { code: '<input type="checkbox" aria-checked />' },
312
+ { code: '<input type="checkbox" aria-controls />' },
313
+ { code: '<input type="checkbox" aria-describedby />' },
314
+ { code: '<input type="checkbox" aria-disabled />' },
315
+ { code: '<input type="checkbox" aria-dropeffect />' },
316
+ { code: '<input type="checkbox" aria-flowto />' },
317
+ { code: '<input type="checkbox" aria-grabbed />' },
318
+ { code: '<input type="checkbox" aria-hidden />' },
319
+ { code: '<input type="checkbox" aria-invalid />' },
320
+ { code: '<input type="checkbox" aria-label />' },
321
+ { code: '<input type="checkbox" aria-labelledby />' },
322
+ { code: '<input type="checkbox" aria-live />' },
323
+ { code: '<input type="checkbox" aria-owns />' },
324
+ { code: '<input type="checkbox" aria-relevant />' },
325
+ // when `type="radio"`, the implicit role is `radio`
326
+ { code: '<input type="radio" aria-atomic />' },
327
+ { code: '<input type="radio" aria-busy />' },
328
+ { code: '<input type="radio" aria-checked />' },
329
+ { code: '<input type="radio" aria-controls />' },
330
+ { code: '<input type="radio" aria-describedby />' },
331
+ { code: '<input type="radio" aria-disabled />' },
332
+ { code: '<input type="radio" aria-dropeffect />' },
333
+ { code: '<input type="radio" aria-flowto />' },
334
+ { code: '<input type="radio" aria-grabbed />' },
335
+ { code: '<input type="radio" aria-hidden />' },
336
+ { code: '<input type="radio" aria-label />' },
337
+ { code: '<input type="radio" aria-labelledby />' },
338
+ { code: '<input type="radio" aria-live />' },
339
+ { code: '<input type="radio" aria-owns />' },
340
+ { code: '<input type="radio" aria-relevant />' },
341
+ { code: '<input type="radio" aria-posinset />' },
342
+ { code: '<input type="radio" aria-setsize />' },
343
+ // when `type="range"`, the implicit role is `slider`
344
+ { code: '<input type="range" aria-valuemax />' },
345
+ { code: '<input type="range" aria-valuemin />' },
346
+ { code: '<input type="range" aria-valuenow />' },
347
+ { code: '<input type="range" aria-orientation />' },
348
+ { code: '<input type="range" aria-atomic />' },
349
+ { code: '<input type="range" aria-busy />' },
350
+ { code: '<input type="range" aria-controls />' },
351
+ { code: '<input type="range" aria-describedby />' },
352
+ { code: '<input type="range" aria-disabled />' },
353
+ { code: '<input type="range" aria-dropeffect />' },
354
+ { code: '<input type="range" aria-flowto />' },
355
+ { code: '<input type="range" aria-grabbed />' },
356
+ { code: '<input type="range" aria-haspopup />' },
357
+ { code: '<input type="range" aria-hidden />' },
358
+ { code: '<input type="range" aria-invalid />' },
359
+ { code: '<input type="range" aria-label />' },
360
+ { code: '<input type="range" aria-labelledby />' },
361
+ { code: '<input type="range" aria-live />' },
362
+ { code: '<input type="range" aria-owns />' },
363
+ { code: '<input type="range" aria-relevant />' },
364
+ { code: '<input type="range" aria-valuetext />' },
365
+
366
+ // these will have role of `textbox`,
367
+ { code: '<input type="email" aria-disabled />' },
368
+ { code: '<input type="password" aria-disabled />' },
369
+ { code: '<input type="search" aria-disabled />' },
370
+ { code: '<input type="tel" aria-disabled />' },
371
+ { code: '<input type="url" aria-disabled />' },
372
+ { code: '<input aria-disabled />' },
373
+
374
+ // Allow null/undefined values regardless of role
375
+ { code: '<h2 role="presentation" aria-level={null} />' },
376
+ { code: '<h2 role="presentation" aria-level={undefined} />' },
377
+
378
+ // OTHER TESTS
379
+ { code: '<button aria-pressed />' },
380
+ { code: '<form aria-hidden />' },
381
+ { code: '<h1 aria-hidden />' },
382
+ { code: '<h2 aria-hidden />' },
383
+ { code: '<h3 aria-hidden />' },
384
+ { code: '<h4 aria-hidden />' },
385
+ { code: '<h5 aria-hidden />' },
386
+ { code: '<h6 aria-hidden />' },
387
+ { code: '<hr aria-hidden />' },
388
+ { code: '<li aria-current />' },
389
+ { code: '<meter aria-atomic />' },
390
+ { code: '<option aria-atomic />' },
391
+ { code: '<progress aria-atomic />' },
392
+ { code: '<textarea aria-hidden />' },
393
+ { code: '<select aria-expanded />' },
394
+ { code: '<datalist aria-expanded />' },
395
+ { code: '<div role="heading" aria-level />' },
396
+ { code: '<div role="heading" aria-level="1" />' },
397
+
398
+ semver.satisfies(eslintVersion, '>= 6') ? {
399
+ code: `
400
+ const HelloThere = () => (
401
+ <Hello
402
+ role="searchbox"
403
+ frag={
404
+ <>
405
+ <div>Hello</div>
406
+ <div>There</div>
407
+ </>
408
+ }
409
+ />
410
+ );
411
+
412
+ const Hello = (props) => <div>{props.frag}</div>;
413
+ `,
414
+ } : [],
415
+ validTests,
416
+ )).map(parserOptionsMapper),
417
+
418
+ invalid: parsers.all([].concat(
419
+ // implicit basic checks
420
+ {
421
+ code: '<a href="#" aria-checked />',
422
+ errors: [errorMessage('aria-checked', 'link', 'a', true)],
423
+ },
424
+ {
425
+ code: '<area href="#" aria-checked />',
426
+ errors: [errorMessage('aria-checked', 'link', 'area', true)],
427
+ },
428
+ {
429
+ code: '<link href="#" aria-checked />',
430
+ errors: [errorMessage('aria-checked', 'link', 'link', true)],
431
+ },
432
+ {
433
+ code: '<img alt="foobar" aria-checked />',
434
+ errors: [errorMessage('aria-checked', 'img', 'img', true)],
435
+ },
436
+ {
437
+ code: '<menu type="toolbar" aria-checked />',
438
+ errors: [errorMessage('aria-checked', 'toolbar', 'menu', true)],
439
+ },
440
+ {
441
+ code: '<aside aria-checked />',
442
+ errors: [errorMessage('aria-checked', 'complementary', 'aside', true)],
443
+ },
444
+ {
445
+ code: '<ul aria-expanded />',
446
+ errors: [errorMessage('aria-expanded', 'list', 'ul', true)],
447
+ },
448
+ {
449
+ code: '<details aria-expanded />',
450
+ errors: [errorMessage('aria-expanded', 'group', 'details', true)],
451
+ },
452
+ {
453
+ code: '<dialog aria-expanded />',
454
+ errors: [errorMessage('aria-expanded', 'dialog', 'dialog', true)],
455
+ },
456
+ {
457
+ code: '<aside aria-expanded />',
458
+ errors: [errorMessage('aria-expanded', 'complementary', 'aside', true)],
459
+ },
460
+ {
461
+ code: '<article aria-expanded />',
462
+ errors: [errorMessage('aria-expanded', 'article', 'article', true)],
463
+ },
464
+ {
465
+ code: '<body aria-expanded />',
466
+ errors: [errorMessage('aria-expanded', 'document', 'body', true)],
467
+ },
468
+ {
469
+ code: '<li aria-expanded />',
470
+ errors: [errorMessage('aria-expanded', 'listitem', 'li', true)],
471
+ },
472
+ {
473
+ code: '<nav aria-expanded />',
474
+ errors: [errorMessage('aria-expanded', 'navigation', 'nav', true)],
475
+ },
476
+ {
477
+ code: '<ol aria-expanded />',
478
+ errors: [errorMessage('aria-expanded', 'list', 'ol', true)],
479
+ },
480
+ {
481
+ code: '<output aria-expanded />',
482
+ errors: [errorMessage('aria-expanded', 'status', 'output', true)],
483
+ },
484
+ {
485
+ code: '<section aria-expanded />',
486
+ errors: [errorMessage('aria-expanded', 'region', 'section', true)],
487
+ },
488
+ {
489
+ code: '<tbody aria-expanded />',
490
+ errors: [errorMessage('aria-expanded', 'rowgroup', 'tbody', true)],
491
+ },
492
+ {
493
+ code: '<tfoot aria-expanded />',
494
+ errors: [errorMessage('aria-expanded', 'rowgroup', 'tfoot', true)],
495
+ },
496
+ {
497
+ code: '<thead aria-expanded />',
498
+ errors: [errorMessage('aria-expanded', 'rowgroup', 'thead', true)],
499
+ },
500
+ {
501
+ code: '<input type="radio" aria-invalid />',
502
+ errors: [errorMessage('aria-invalid', 'radio', 'input', true)],
503
+ },
504
+ {
505
+ code: '<input type="radio" aria-selected />',
506
+ errors: [errorMessage('aria-selected', 'radio', 'input', true)],
507
+ },
508
+ {
509
+ code: '<input type="radio" aria-haspopup />',
510
+ errors: [errorMessage('aria-haspopup', 'radio', 'input', true)],
511
+ },
512
+ {
513
+ code: '<input type="checkbox" aria-haspopup />',
514
+ errors: [errorMessage('aria-haspopup', 'checkbox', 'input', true)],
515
+ },
516
+ {
517
+ code: '<input type="reset" aria-invalid />',
518
+ errors: [errorMessage('aria-invalid', 'button', 'input', true)],
519
+ },
520
+ {
521
+ code: '<input type="submit" aria-invalid />',
522
+ errors: [errorMessage('aria-invalid', 'button', 'input', true)],
523
+ },
524
+ {
525
+ code: '<input type="image" aria-invalid />',
526
+ errors: [errorMessage('aria-invalid', 'button', 'input', true)],
527
+ },
528
+ {
529
+ code: '<input type="button" aria-invalid />',
530
+ errors: [errorMessage('aria-invalid', 'button', 'input', true)],
531
+ },
532
+ {
533
+ code: '<menuitem type="command" aria-invalid />',
534
+ errors: [errorMessage('aria-invalid', 'menuitem', 'menuitem', true)],
535
+ },
536
+ {
537
+ code: '<menuitem type="radio" aria-selected />',
538
+ errors: [errorMessage('aria-selected', 'menuitemradio', 'menuitem', true)],
539
+ },
540
+ {
541
+ code: '<menu type="toolbar" aria-haspopup />',
542
+ errors: [errorMessage('aria-haspopup', 'toolbar', 'menu', true)],
543
+ },
544
+ {
545
+ code: '<menu type="toolbar" aria-invalid />',
546
+ errors: [errorMessage('aria-invalid', 'toolbar', 'menu', true)],
547
+ },
548
+ {
549
+ code: '<menu type="toolbar" aria-expanded />',
550
+ errors: [errorMessage('aria-expanded', 'toolbar', 'menu', true)],
551
+ },
552
+ {
553
+ code: '<link href="#" aria-invalid />',
554
+ errors: [errorMessage('aria-invalid', 'link', 'link', true)],
555
+ },
556
+ {
557
+ code: '<area href="#" aria-invalid />',
558
+ errors: [errorMessage('aria-invalid', 'link', 'area', true)],
559
+ },
560
+ {
561
+ code: '<a href="#" aria-invalid />',
562
+ errors: [errorMessage('aria-invalid', 'link', 'a', true)],
563
+ },
564
+ {
565
+ code: '<Link href="#" aria-checked />',
566
+ errors: [errorMessage('aria-checked', 'link', 'a', true)],
567
+ settings: componentsSettings,
568
+ },
569
+ )).concat(invalidTests).map(parserOptionsMapper),
570
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @fileoverview Enforce scope prop is only used on <th> elements.
3
+ * @author Ethan Cohen
4
+ */
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Requirements
8
+ // -----------------------------------------------------------------------------
9
+
10
+ import { RuleTester } from 'eslint';
11
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
12
+ import parsers from '../../__util__/helpers/parsers';
13
+ import rule from '../../../src/rules/scope';
14
+
15
+ // -----------------------------------------------------------------------------
16
+ // Tests
17
+ // -----------------------------------------------------------------------------
18
+
19
+ const ruleTester = new RuleTester();
20
+
21
+ const expectedError = {
22
+ message: 'The scope prop can only be used on <th> elements.',
23
+ type: 'JSXAttribute',
24
+ };
25
+
26
+ const componentsSettings = {
27
+ 'jsx-a11y': {
28
+ components: {
29
+ Foo: 'div',
30
+ TableHeader: 'th',
31
+ },
32
+ },
33
+ };
34
+
35
+ ruleTester.run('scope', rule, {
36
+ valid: parsers.all([].concat(
37
+ { code: '<div />;' },
38
+ { code: '<div foo />;' },
39
+ { code: '<th scope />' },
40
+ { code: '<th scope="row" />' },
41
+ { code: '<th scope={foo} />' },
42
+ { code: '<th scope={"col"} {...props} />' },
43
+ { code: '<Foo scope="bar" {...props} />' },
44
+ { code: '<TableHeader scope="row" />', settings: componentsSettings },
45
+ )).map(parserOptionsMapper),
46
+ invalid: parsers.all([].concat(
47
+ { code: '<div scope />', errors: [expectedError] },
48
+ { code: '<Foo scope="bar" />', settings: componentsSettings, errors: [expectedError] },
49
+ )).map(parserOptionsMapper),
50
+ });