@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,500 @@
1
+ /**
2
+ * @fileoverview Disallow inherently non-interactive elements to be assigned
3
+ * interactive roles.
4
+ * @author Jesse Beach
5
+ * @author $AUTHOR
6
+ */
7
+
8
+ // -----------------------------------------------------------------------------
9
+ // Requirements
10
+ // -----------------------------------------------------------------------------
11
+
12
+ import { RuleTester } from 'eslint';
13
+ import { configs } from '../../../src/index';
14
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
15
+ import parsers from '../../__util__/helpers/parsers';
16
+ import rule from '../../../src/rules/no-noninteractive-element-to-interactive-role';
17
+ import ruleOptionsMapperFactory from '../../__util__/ruleOptionsMapperFactory';
18
+
19
+ // -----------------------------------------------------------------------------
20
+ // Tests
21
+ // -----------------------------------------------------------------------------
22
+
23
+ const ruleTester = new RuleTester();
24
+
25
+ const errorMessage = 'Non-interactive elements should not be assigned interactive roles.';
26
+
27
+ const expectedError = {
28
+ message: errorMessage,
29
+ type: 'JSXAttribute',
30
+ };
31
+
32
+ const ruleName = 'jsx-a11y/no-noninteractive-element-to-interactive-role';
33
+
34
+ const componentsSettings = {
35
+ 'jsx-a11y': {
36
+ components: {
37
+ Article: 'article',
38
+ Input: 'input',
39
+ },
40
+ },
41
+ };
42
+
43
+ const alwaysValid = [
44
+ { code: '<TestComponent onClick={doFoo} />' },
45
+ { code: '<Button onClick={doFoo} />' },
46
+ /* Interactive elements */
47
+ { code: '<a tabIndex="0" role="button" />' },
48
+ { code: '<a href="http://x.y.z" role="button" />' },
49
+ { code: '<a href="http://x.y.z" tabIndex="0" role="button" />' },
50
+ { code: '<area role="button" />;' },
51
+ { code: '<area role="menuitem" />;' },
52
+ { code: '<button className="foo" role="button" />' },
53
+ { code: '<body role="button" />;' },
54
+ { code: '<frame role="button" />;' },
55
+ { code: '<td role="button" />;' },
56
+ { code: '<frame role="menuitem" />;' },
57
+ { code: '<td role="menuitem" />;' },
58
+ /* All flavors of input */
59
+ { code: '<input role="button" />' },
60
+ { code: '<input type="button" role="button" />' },
61
+ { code: '<input type="checkbox" role="button" />' },
62
+ { code: '<input type="color" role="button" />' },
63
+ { code: '<input type="date" role="button" />' },
64
+ { code: '<input type="datetime" role="button" />' },
65
+ { code: '<input type="datetime-local" role="button" />' },
66
+ { code: '<input type="email" role="button" />' },
67
+ { code: '<input type="file" role="button" />' },
68
+ { code: '<input type="hidden" role="button" />' },
69
+ { code: '<input type="image" role="button" />' },
70
+ { code: '<input type="month" role="button" />' },
71
+ { code: '<input type="number" role="button" />' },
72
+ { code: '<input type="password" role="button" />' },
73
+ { code: '<input type="radio" role="button" />' },
74
+ { code: '<input type="range" role="button" />' },
75
+ { code: '<input type="reset" role="button" />' },
76
+ { code: '<input type="search" role="button" />' },
77
+ { code: '<input type="submit" role="button" />' },
78
+ { code: '<input type="tel" role="button" />' },
79
+ { code: '<input type="text" role="button" />' },
80
+ { code: '<input type="time" role="button" />' },
81
+ { code: '<input type="url" role="button" />' },
82
+ { code: '<input type="week" role="button" />' },
83
+ { code: '<input type="hidden" role="img" />' },
84
+ /* End all flavors of input */
85
+ { code: '<menuitem role="button" />;' },
86
+ { code: '<option className="foo" role="button" />' },
87
+ { code: '<select className="foo" role="button" />' },
88
+ { code: '<textarea className="foo" role="button" />' },
89
+ { code: '<tr role="button" />;' },
90
+ { code: '<tr role="presentation" />;' },
91
+ /* Interactive elements */
92
+ { code: '<a tabIndex="0" role="img" />' },
93
+ { code: '<a href="http://x.y.z" role="img" />' },
94
+ { code: '<a href="http://x.y.z" tabIndex="0" role="img" />' },
95
+ /* All flavors of input */
96
+ { code: '<input role="img" />' },
97
+ { code: '<input type="img" role="img" />' },
98
+ { code: '<input type="checkbox" role="img" />' },
99
+ { code: '<input type="color" role="img" />' },
100
+ { code: '<input type="date" role="img" />' },
101
+ { code: '<input type="datetime" role="img" />' },
102
+ { code: '<input type="datetime-local" role="img" />' },
103
+ { code: '<input type="email" role="img" />' },
104
+ { code: '<input type="file" role="img" />' },
105
+ { code: '<input type="hidden" role="button" />' },
106
+ { code: '<input type="image" role="img" />' },
107
+ { code: '<input type="month" role="img" />' },
108
+ { code: '<input type="number" role="img" />' },
109
+ { code: '<input type="password" role="img" />' },
110
+ { code: '<input type="radio" role="img" />' },
111
+ { code: '<input type="range" role="img" />' },
112
+ { code: '<input type="reset" role="img" />' },
113
+ { code: '<input type="search" role="img" />' },
114
+ { code: '<input type="submit" role="img" />' },
115
+ { code: '<input type="tel" role="img" />' },
116
+ { code: '<input type="text" role="img" />' },
117
+ { code: '<input type="time" role="img" />' },
118
+ { code: '<input type="url" role="img" />' },
119
+ { code: '<input type="week" role="img" />' },
120
+ /* End all flavors of input */
121
+ { code: '<menuitem role="img" />;' },
122
+ { code: '<option className="foo" role="img" />' },
123
+ { code: '<select className="foo" role="img" />' },
124
+ { code: '<textarea className="foo" role="img" />' },
125
+ { code: '<tr role="img" />;' },
126
+ /* Interactive elements */
127
+ { code: '<a tabIndex="0" role="listitem" />' },
128
+ { code: '<a href="http://x.y.z" role="listitem" />' },
129
+ { code: '<a href="http://x.y.z" tabIndex="0" role="listitem" />' },
130
+ /* All flavors of input */
131
+ { code: '<input role="listitem" />' },
132
+ { code: '<input type="listitem" role="listitem" />' },
133
+ { code: '<input type="checkbox" role="listitem" />' },
134
+ { code: '<input type="color" role="listitem" />' },
135
+ { code: '<input type="date" role="listitem" />' },
136
+ { code: '<input type="datetime" role="listitem" />' },
137
+ { code: '<input type="datetime-local" role="listitem" />' },
138
+ { code: '<input type="email" role="listitem" />' },
139
+ { code: '<input type="file" role="listitem" />' },
140
+ { code: '<input type="image" role="listitem" />' },
141
+ { code: '<input type="month" role="listitem" />' },
142
+ { code: '<input type="number" role="listitem" />' },
143
+ { code: '<input type="password" role="listitem" />' },
144
+ { code: '<input type="radio" role="listitem" />' },
145
+ { code: '<input type="range" role="listitem" />' },
146
+ { code: '<input type="reset" role="listitem" />' },
147
+ { code: '<input type="search" role="listitem" />' },
148
+ { code: '<input type="submit" role="listitem" />' },
149
+ { code: '<input type="tel" role="listitem" />' },
150
+ { code: '<input type="text" role="listitem" />' },
151
+ { code: '<input type="time" role="listitem" />' },
152
+ { code: '<input type="url" role="listitem" />' },
153
+ { code: '<input type="week" role="listitem" />' },
154
+ /* End all flavors of input */
155
+ { code: '<menuitem role="listitem" />;' },
156
+ { code: '<option className="foo" role="listitem" />' },
157
+ { code: '<select className="foo" role="listitem" />' },
158
+ { code: '<textarea className="foo" role="listitem" />' },
159
+ { code: '<tr role="listitem" />;' },
160
+ /* HTML elements with neither an interactive or non-interactive valence (static) */
161
+ { code: '<acronym role="button" />;' },
162
+ { code: '<applet role="button" />;' },
163
+ { code: '<audio role="button" />;' },
164
+ { code: '<b role="button" />;' },
165
+ { code: '<base role="button" />;' },
166
+ { code: '<bdi role="button" />;' },
167
+ { code: '<bdo role="button" />;' },
168
+ { code: '<big role="button" />;' },
169
+ { code: '<blink role="button" />;' },
170
+ { code: '<canvas role="button" />;' },
171
+ { code: '<center role="button" />;' },
172
+ { code: '<cite role="button" />;' },
173
+ { code: '<col role="button" />;' },
174
+ { code: '<colgroup role="button" />;' },
175
+ { code: '<content role="button" />;' },
176
+ { code: '<data role="button" />;' },
177
+ { code: '<datalist role="button" />;' },
178
+ { code: '<div role="button" />;' },
179
+ { code: '<div className="foo" role="button" />;' },
180
+ { code: '<div className="foo" {...props} role="button" />;' },
181
+ { code: '<div aria-hidden role="button" />;' },
182
+ { code: '<div aria-hidden={true} role="button" />;' },
183
+ { code: '<div role="button" />;' },
184
+ { code: '<div role={undefined} role="button" />;' },
185
+ { code: '<div {...props} role="button" />;' },
186
+ { code: '<div onKeyUp={() => void 0} aria-hidden={false} role="button" />;' },
187
+ { code: '<embed role="button" />;' },
188
+ { code: '<font role="button" />;' },
189
+ { code: '<frameset role="button" />;' },
190
+ { code: '<head role="button" />;' },
191
+ { code: '<header role="button" />;' },
192
+ { code: '<hgroup role="button" />;' },
193
+ { code: '<i role="button" />;' },
194
+ { code: '<kbd role="button" />;' },
195
+ { code: '<keygen role="button" />;' },
196
+ { code: '<link role="button" />;' },
197
+ { code: '<map role="button" />;' },
198
+ { code: '<meta role="button" />;' },
199
+ { code: '<noembed role="button" />;' },
200
+ { code: '<noscript role="button" />;' },
201
+ { code: '<object role="button" />;' },
202
+ { code: '<param role="button" />;' },
203
+ { code: '<picture role="button" />;' },
204
+ { code: '<q role="button" />;' },
205
+ { code: '<rp role="button" />;' },
206
+ { code: '<rt role="button" />;' },
207
+ { code: '<rtc role="button" />;' },
208
+ { code: '<s role="button" />;' },
209
+ { code: '<samp role="button" />;' },
210
+ { code: '<script role="button" />;' },
211
+ { code: '<small role="button" />;' },
212
+ { code: '<source role="button" />;' },
213
+ { code: '<spacer role="button" />;' },
214
+ { code: '<span role="button" />;' },
215
+ { code: '<strike role="button" />;' },
216
+ { code: '<style role="button" />;' },
217
+ { code: '<summary role="button" />;' },
218
+ { code: '<th role="button" />;' },
219
+ { code: '<title role="button" />;' },
220
+ { code: '<track role="button" />;' },
221
+ { code: '<tt role="button" />;' },
222
+ { code: '<u role="button" />;' },
223
+ { code: '<var role="button" />;' },
224
+ { code: '<video role="button" />;' },
225
+ { code: '<wbr role="button" />;' },
226
+ { code: '<xmp role="button" />;' },
227
+ /* HTML elements attributed with an interactive role */
228
+ { code: '<div role="button" />;' },
229
+ { code: '<div role="checkbox" />;' },
230
+ { code: '<div role="columnheader" />;' },
231
+ { code: '<div role="combobox" />;' },
232
+ { code: '<div role="grid" />;' },
233
+ { code: '<div role="gridcell" />;' },
234
+ { code: '<div role="link" />;' },
235
+ { code: '<div role="listbox" />;' },
236
+ { code: '<div role="menu" />;' },
237
+ { code: '<div role="menubar" />;' },
238
+ { code: '<div role="menuitem" />;' },
239
+ { code: '<div role="menuitemcheckbox" />;' },
240
+ { code: '<div role="menuitemradio" />;' },
241
+ { code: '<div role="option" />;' },
242
+ { code: '<div role="progressbar" />;' },
243
+ { code: '<div role="radio" />;' },
244
+ { code: '<div role="radiogroup" />;' },
245
+ { code: '<div role="row" />;' },
246
+ { code: '<div role="rowheader" />;' },
247
+ { code: '<div role="searchbox" />;' },
248
+ { code: '<div role="slider" />;' },
249
+ { code: '<div role="spinbutton" />;' },
250
+ { code: '<div role="switch" />;' },
251
+ { code: '<div role="tab" />;' },
252
+ { code: '<div role="textbox" />;' },
253
+ { code: '<div role="treeitem" />;' },
254
+ /* Presentation is a special case role that indicates intentional static semantics */
255
+ { code: '<div role="presentation" />;' },
256
+ /* HTML elements attributed with an abstract role */
257
+ { code: '<div role="command" />;' },
258
+ { code: '<div role="composite" />;' },
259
+ { code: '<div role="input" />;' },
260
+ { code: '<div role="landmark" />;' },
261
+ { code: '<div role="range" />;' },
262
+ { code: '<div role="roletype" />;' },
263
+ { code: '<div role="section" />;' },
264
+ { code: '<div role="sectionhead" />;' },
265
+ { code: '<div role="select" />;' },
266
+ { code: '<div role="structure" />;' },
267
+ { code: '<div role="tablist" />;' },
268
+ { code: '<div role="toolbar" />;' },
269
+ { code: '<div role="tree" />;' },
270
+ { code: '<div role="treegrid" />;' },
271
+ { code: '<div role="widget" />;' },
272
+ { code: '<div role="window" />;' },
273
+ /* HTML elements with an inherent non-interactive role, assigned an
274
+ * interactive role. */
275
+ { code: '<main role="listitem" />;' },
276
+ { code: '<a role="listitem" />' },
277
+ { code: '<a role="listitem" />;' },
278
+ { code: '<a role="button" />' },
279
+ { code: '<a role="button" />;' },
280
+ { code: '<a role="menuitem" />' },
281
+ { code: '<a role="menuitem" />;' },
282
+ { code: '<area role="listitem" />;' },
283
+ { code: '<article role="listitem" />;' },
284
+ { code: '<article role="listitem" />;' },
285
+ { code: '<dd role="listitem" />;' },
286
+ { code: '<dfn role="listitem" />;' },
287
+ { code: '<dt role="listitem" />;' },
288
+ { code: '<fieldset role="listitem" />;' },
289
+ { code: '<figure role="listitem" />;' },
290
+ { code: '<form role="listitem" />;' },
291
+ { code: '<frame role="listitem" />;' },
292
+ { code: '<h1 role="listitem" />;' },
293
+ { code: '<h2 role="listitem" />;' },
294
+ { code: '<h3 role="listitem" />;' },
295
+ { code: '<h4 role="listitem" />;' },
296
+ { code: '<h5 role="listitem" />;' },
297
+ { code: '<h6 role="listitem" />;' },
298
+ { code: '<hr role="listitem" />;' },
299
+ { code: '<img role="listitem" />;' },
300
+ { code: '<li role="listitem" />;' },
301
+ { code: '<li role="presentation" />;' },
302
+ { code: '<nav role="listitem" />;' },
303
+ { code: '<ol role="listitem" />;' },
304
+ { code: '<table role="listitem" />;' },
305
+ { code: '<tbody role="listitem" />;' },
306
+ { code: '<td role="listitem" />;' },
307
+ { code: '<tfoot role="listitem" />;' },
308
+ { code: '<thead role="listitem" />;' },
309
+ { code: '<ul role="listitem" />;' },
310
+ /* HTML elements attributed with a non-interactive role */
311
+ { code: '<div role="alert" />;' },
312
+ { code: '<div role="alertdialog" />;' },
313
+ { code: '<div role="application" />;' },
314
+ { code: '<div role="article" />;' },
315
+ { code: '<div role="banner" />;' },
316
+ { code: '<div role="cell" />;' },
317
+ { code: '<div role="complementary" />;' },
318
+ { code: '<div role="contentinfo" />;' },
319
+ { code: '<div role="definition" />;' },
320
+ { code: '<div role="dialog" />;' },
321
+ { code: '<div role="directory" />;' },
322
+ { code: '<div role="document" />;' },
323
+ { code: '<div role="feed" />;' },
324
+ { code: '<div role="figure" />;' },
325
+ { code: '<div role="form" />;' },
326
+ { code: '<div role="group" />;' },
327
+ { code: '<div role="heading" />;' },
328
+ { code: '<div role="img" />;' },
329
+ { code: '<div role="list" />;' },
330
+ { code: '<div role="listitem" />;' },
331
+ { code: '<div role="log" />;' },
332
+ { code: '<div role="main" />;' },
333
+ { code: '<div role="marquee" />;' },
334
+ { code: '<div role="math" />;' },
335
+ { code: '<div role="navigation" />;' },
336
+ { code: '<div role="note" />;' },
337
+ { code: '<div role="region" />;' },
338
+ { code: '<div role="rowgroup" />;' },
339
+ { code: '<div role="search" />;' },
340
+ { code: '<div role="separator" />;' },
341
+ { code: '<div role="scrollbar" />;' },
342
+ { code: '<div role="status" />;' },
343
+ { code: '<div role="table" />;' },
344
+ { code: '<div role="tabpanel" />;' },
345
+ { code: '<div role="term" />;' },
346
+ { code: '<div role="timer" />;' },
347
+ { code: '<div role="tooltip" />;' },
348
+ { code: '<ul role="list" />;' },
349
+ /* Custom components */
350
+ { code: '<Article role="button" />' },
351
+ { code: '<Input role="button" />', settings: componentsSettings },
352
+ ];
353
+
354
+ const neverValid = [
355
+ /* HTML elements with an inherent non-interactive role, assigned an
356
+ * interactive role. */
357
+ { code: '<address role="button" />;', errors: [expectedError] },
358
+ { code: '<article role="button" />;', errors: [expectedError] },
359
+ { code: '<aside role="button" />;', errors: [expectedError] },
360
+ { code: '<blockquote role="button" />;', errors: [expectedError] },
361
+ { code: '<br role="button" />;', errors: [expectedError] },
362
+ { code: '<caption role="button" />;', errors: [expectedError] },
363
+ { code: '<code role="button" />;', errors: [expectedError] },
364
+ { code: '<dd role="button" />;', errors: [expectedError] },
365
+ { code: '<del role="button" />;', errors: [expectedError] },
366
+ { code: '<details role="button" />;', errors: [expectedError] },
367
+ { code: '<dfn role="button" />;', errors: [expectedError] },
368
+ { code: '<dir role="button" />;', errors: [expectedError] },
369
+ { code: '<dl role="button" />;', errors: [expectedError] },
370
+ { code: '<dt role="button" />;', errors: [expectedError] },
371
+ { code: '<em role="button" />;', errors: [expectedError] },
372
+ { code: '<fieldset role="button" />;', errors: [expectedError] },
373
+ { code: '<figcaption role="button" />;', errors: [expectedError] },
374
+ { code: '<figure role="button" />;', errors: [expectedError] },
375
+ { code: '<footer role="button" />;', errors: [expectedError] },
376
+ { code: '<form role="button" />;', errors: [expectedError] },
377
+ { code: '<h1 role="button" />;', errors: [expectedError] },
378
+ { code: '<h2 role="button" />;', errors: [expectedError] },
379
+ { code: '<h3 role="button" />;', errors: [expectedError] },
380
+ { code: '<h4 role="button" />;', errors: [expectedError] },
381
+ { code: '<h5 role="button" />;', errors: [expectedError] },
382
+ { code: '<h6 role="button" />;', errors: [expectedError] },
383
+ { code: '<hr role="button" />;', errors: [expectedError] },
384
+ { code: '<html role="button" />;', errors: [expectedError] },
385
+ { code: '<iframe role="button" />;', errors: [expectedError] },
386
+ { code: '<img role="button" />;', errors: [expectedError] },
387
+ { code: '<ins role="button" />;', errors: [expectedError] },
388
+ { code: '<label role="button" />;', errors: [expectedError] },
389
+ { code: '<legend role="button" />;', errors: [expectedError] },
390
+ { code: '<li role="button" />;', errors: [expectedError] },
391
+ { code: '<main role="button" />;', errors: [expectedError] },
392
+ { code: '<mark role="button" />;', errors: [expectedError] },
393
+ { code: '<marquee role="button" />;', errors: [expectedError] },
394
+ { code: '<menu role="button" />;', errors: [expectedError] },
395
+ { code: '<meter role="button" />;', errors: [expectedError] },
396
+ { code: '<nav role="button" />;', errors: [expectedError] },
397
+ { code: '<ol role="button" />;', errors: [expectedError] },
398
+ { code: '<optgroup role="button" />;', errors: [expectedError] },
399
+ { code: '<output role="button" />;', errors: [expectedError] },
400
+ { code: '<pre role="button" />;', errors: [expectedError] },
401
+ { code: '<progress role="button" />;', errors: [expectedError] },
402
+ { code: '<ruby role="button" />;', errors: [expectedError] },
403
+ { code: '<strong role="button" />;', errors: [expectedError] },
404
+ { code: '<sub role="button" />;', errors: [expectedError] },
405
+ { code: '<sup role="button" />;', errors: [expectedError] },
406
+ { code: '<table role="button" />;', errors: [expectedError] },
407
+ { code: '<tbody role="button" />;', errors: [expectedError] },
408
+ { code: '<tfoot role="button" />;', errors: [expectedError] },
409
+ { code: '<thead role="button" />;', errors: [expectedError] },
410
+ { code: '<time role="button" />;', errors: [expectedError] },
411
+ { code: '<ul role="button" />;', errors: [expectedError] },
412
+ /* HTML elements with an inherent non-interactive role, assigned an
413
+ * interactive role. */
414
+ { code: '<main role="menuitem" />;', errors: [expectedError] },
415
+ { code: '<article role="menuitem" />;', errors: [expectedError] },
416
+ { code: '<dd role="menuitem" />;', errors: [expectedError] },
417
+ { code: '<dfn role="menuitem" />;', errors: [expectedError] },
418
+ { code: '<dt role="menuitem" />;', errors: [expectedError] },
419
+ { code: '<fieldset role="menuitem" />;', errors: [expectedError] },
420
+ { code: '<figure role="menuitem" />;', errors: [expectedError] },
421
+ { code: '<form role="menuitem" />;', errors: [expectedError] },
422
+ { code: '<h1 role="menuitem" />;', errors: [expectedError] },
423
+ { code: '<h2 role="menuitem" />;', errors: [expectedError] },
424
+ { code: '<h3 role="menuitem" />;', errors: [expectedError] },
425
+ { code: '<h4 role="menuitem" />;', errors: [expectedError] },
426
+ { code: '<h5 role="menuitem" />;', errors: [expectedError] },
427
+ { code: '<h6 role="menuitem" />;', errors: [expectedError] },
428
+ { code: '<hr role="menuitem" />;', errors: [expectedError] },
429
+ { code: '<img role="menuitem" />;', errors: [expectedError] },
430
+ { code: '<nav role="menuitem" />;', errors: [expectedError] },
431
+ { code: '<ol role="menuitem" />;', errors: [expectedError] },
432
+ { code: '<p role="button" />;', errors: [expectedError] },
433
+ { code: '<section role="button" aria-label="Aardvark" />;', errors: [expectedError] },
434
+ { code: '<table role="menuitem" />;', errors: [expectedError] },
435
+ { code: '<tbody role="menuitem" />;', errors: [expectedError] },
436
+ { code: '<tfoot role="menuitem" />;', errors: [expectedError] },
437
+ { code: '<thead role="menuitem" />;', errors: [expectedError] },
438
+ /* Custom components */
439
+ { code: '<Article role="button" />', errors: [expectedError], settings: componentsSettings },
440
+ ];
441
+
442
+ const recommendedOptions = (configs.recommended.rules[ruleName][1] || {});
443
+ ruleTester.run(`${ruleName}:recommended`, rule, {
444
+ valid: parsers.all([].concat(
445
+ ...alwaysValid,
446
+ { code: '<ul role="menu" />;' },
447
+ { code: '<ul role="menubar" />;' },
448
+ { code: '<ul role="radiogroup" />;' },
449
+ { code: '<ul role="tablist" />;' },
450
+ { code: '<ul role="tree" />;' },
451
+ { code: '<ul role="treegrid" />;' },
452
+ { code: '<ol role="menu" />;' },
453
+ { code: '<ol role="menubar" />;' },
454
+ { code: '<ol role="radiogroup" />;' },
455
+ { code: '<ol role="tablist" />;' },
456
+ { code: '<ol role="tree" />;' },
457
+ { code: '<ol role="treegrid" />;' },
458
+ { code: '<li role="tab" />;' },
459
+ { code: '<li role="menuitem" />;' },
460
+ { code: '<li role="menuitemcheckbox" />;' },
461
+ { code: '<li role="menuitemradio" />;' },
462
+ { code: '<li role="row" />;' },
463
+ { code: '<li role="treeitem" />;' },
464
+ { code: '<Component role="treeitem" />;' },
465
+ { code: '<fieldset role="radiogroup" />;' },
466
+ { code: '<fieldset role="presentation" />;' },
467
+ ))
468
+ .map(ruleOptionsMapperFactory(recommendedOptions))
469
+ .map(parserOptionsMapper),
470
+ invalid: parsers.all([].concat(
471
+ ...neverValid,
472
+ ))
473
+ .map(ruleOptionsMapperFactory(recommendedOptions))
474
+ .map(parserOptionsMapper),
475
+ });
476
+
477
+ ruleTester.run(`${ruleName}:strict`, rule, {
478
+ valid: parsers.all([].concat(
479
+ ...alwaysValid,
480
+ )).map(parserOptionsMapper),
481
+ invalid: parsers.all([].concat(
482
+ ...neverValid,
483
+ { code: '<ul role="menu" />;', errors: [expectedError] },
484
+ { code: '<ul role="menubar" />;', errors: [expectedError] },
485
+ { code: '<ul role="radiogroup" />;', errors: [expectedError] },
486
+ { code: '<ul role="tablist" />;', errors: [expectedError] },
487
+ { code: '<ul role="tree" />;', errors: [expectedError] },
488
+ { code: '<ul role="treegrid" />;', errors: [expectedError] },
489
+ { code: '<ol role="menu" />;', errors: [expectedError] },
490
+ { code: '<ol role="menubar" />;', errors: [expectedError] },
491
+ { code: '<ol role="radiogroup" />;', errors: [expectedError] },
492
+ { code: '<ol role="tablist" />;', errors: [expectedError] },
493
+ { code: '<ol role="tree" />;', errors: [expectedError] },
494
+ { code: '<ol role="treegrid" />;', errors: [expectedError] },
495
+ { code: '<li role="tab" />;', errors: [expectedError] },
496
+ { code: '<li role="menuitem" />;', errors: [expectedError] },
497
+ { code: '<li role="row" />;', errors: [expectedError] },
498
+ { code: '<li role="treeitem" />;', errors: [expectedError] },
499
+ )).map(parserOptionsMapper),
500
+ });
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Disallow tabindex on static and noninteractive elements
3
+ * @author jessebeach
4
+ */
5
+
6
+ // -----------------------------------------------------------------------------
7
+ // Requirements
8
+ // -----------------------------------------------------------------------------
9
+
10
+ import { RuleTester } from 'eslint';
11
+ import { configs } from '../../../src/index';
12
+ import parserOptionsMapper from '../../__util__/parserOptionsMapper';
13
+ import parsers from '../../__util__/helpers/parsers';
14
+ import rule from '../../../src/rules/no-noninteractive-tabindex';
15
+ import ruleOptionsMapperFactory from '../../__util__/ruleOptionsMapperFactory';
16
+
17
+ // -----------------------------------------------------------------------------
18
+ // Tests
19
+ // -----------------------------------------------------------------------------
20
+
21
+ const ruleTester = new RuleTester();
22
+
23
+ const ruleName = 'no-noninteractive-tabindex';
24
+
25
+ const expectedError = {
26
+ message: '`tabIndex` should only be declared on interactive elements.',
27
+ type: 'JSXAttribute',
28
+ };
29
+
30
+ const componentsSettings = {
31
+ 'jsx-a11y': {
32
+ components: {
33
+ Article: 'article',
34
+ MyButton: 'button',
35
+ },
36
+ },
37
+ };
38
+
39
+ const alwaysValid = [
40
+ { code: '<MyButton tabIndex={0} />' },
41
+ { code: '<button />' },
42
+ { code: '<button tabIndex="0" />' },
43
+ { code: '<button tabIndex={0} />' },
44
+ { code: '<div />' },
45
+ { code: '<div tabIndex="-1" />' },
46
+ { code: '<div role="button" tabIndex="0" />' },
47
+ { code: '<div role="article" tabIndex="-1" />' },
48
+ { code: '<article tabIndex="-1" />' },
49
+ { code: '<Article tabIndex="-1" />', settings: componentsSettings },
50
+ { code: '<MyButton tabIndex={0} />', settings: componentsSettings },
51
+ ];
52
+
53
+ const neverValid = [
54
+ { code: '<div tabIndex="0" />', errors: [expectedError] },
55
+ { code: '<div role="article" tabIndex="0" />', errors: [expectedError] },
56
+ { code: '<article tabIndex="0" />', errors: [expectedError] },
57
+ { code: '<article tabIndex={0} />', errors: [expectedError] },
58
+ { code: '<Article tabIndex={0} />', errors: [expectedError], settings: componentsSettings },
59
+ ];
60
+
61
+ const recommendedOptions = (
62
+ configs.recommended.rules[`jsx-a11y/${ruleName}`][1] || {}
63
+ );
64
+
65
+ ruleTester.run(`${ruleName}:recommended`, rule, {
66
+ valid: parsers.all([].concat(
67
+ ...alwaysValid,
68
+ { code: '<div role="tabpanel" tabIndex="0" />' },
69
+ // Expressions should pass in recommended mode
70
+ { code: '<div role={ROLE_BUTTON} onClick={() => {}} tabIndex="0" />;' },
71
+ // Cases for allowExpressionValues set to true
72
+ {
73
+ code: '<div role={BUTTON} onClick={() => {}} tabIndex="0" />;',
74
+ options: [{ allowExpressionValues: true }],
75
+ },
76
+ // Specific case for ternary operator with literals on both side
77
+ {
78
+ code: '<div role={isButton ? "button" : "link"} onClick={() => {}} tabIndex="0" />;',
79
+ options: [{ allowExpressionValues: true }],
80
+ },
81
+ {
82
+ code: '<div role={isButton ? "button" : LINK} onClick={() => {}} tabIndex="0" />;',
83
+ options: [{ allowExpressionValues: true }],
84
+ errors: [expectedError],
85
+ },
86
+ {
87
+ code: '<div role={isButton ? BUTTON : LINK} onClick={() => {}} tabIndex="0"/>;',
88
+ options: [{ allowExpressionValues: true }],
89
+ errors: [expectedError],
90
+ },
91
+ ))
92
+ .map(ruleOptionsMapperFactory(recommendedOptions))
93
+ .map(parserOptionsMapper),
94
+ invalid: parsers.all([].concat(
95
+ ...neverValid,
96
+ ))
97
+ .map(ruleOptionsMapperFactory(recommendedOptions))
98
+ .map(parserOptionsMapper),
99
+ });
100
+
101
+ ruleTester.run(`${ruleName}:strict`, rule, {
102
+ valid: parsers.all([].concat(
103
+ ...alwaysValid,
104
+ )).map(parserOptionsMapper),
105
+ invalid: parsers.all([].concat(
106
+ ...neverValid,
107
+ { code: '<div role="tabpanel" tabIndex="0" />', errors: [expectedError] },
108
+ // Expressions should fail in strict mode
109
+ { code: '<div role={ROLE_BUTTON} onClick={() => {}} tabIndex="0" />;', errors: [expectedError] },
110
+ // Cases for allowExpressionValues set to false
111
+ {
112
+ code: '<div role={BUTTON} onClick={() => {}} tabIndex="0" />;',
113
+ options: [{ allowExpressionValues: false }],
114
+ errors: [expectedError],
115
+ },
116
+ // Specific case for ternary operator with literals on both side
117
+ {
118
+ code: '<div role={isButton ? "button" : "link"} onClick={() => {}} tabIndex="0" />;',
119
+ options: [{ allowExpressionValues: false }],
120
+ errors: [expectedError],
121
+ },
122
+ )).map(parserOptionsMapper),
123
+ });