Skip to content

Commit bc3e08c

Browse files
authored
Merge branch 'master' into upgrade-dependencies
2 parents 9225067 + bc1a304 commit bc3e08c

30 files changed

+214
-58
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@ matrix:
4646
- node_js: "5"
4747
- node_js: "4" # not sure why this is failing; probably something in jest
4848
env: ESLINT=4
49+
- node_js: "4"
50+
env: ESLINT=3

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
6.2.1 / 2019-02-03
2+
=================
3+
- 9980e45 [fix] Prevent Error when JSXSpreadAttribute is passed to isSemanticRoleElement
4+
15
6.2.0 / 2019-01-25
26
=================
37
- 5650674 [new rule] control-has-associated-label checks interactives for a label

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Add `plugin:jsx-a11y/recommended` or `plugin:jsx-a11y/strict` in `extends`:
107107
- [iframe-has-title](docs/rules/iframe-has-title.md): Enforce iframe elements have a title attribute.
108108
- [img-redundant-alt](docs/rules/img-redundant-alt.md): Enforce `<img>` alt prop does not contain the word "image", "picture", or "photo".
109109
- [interactive-supports-focus](docs/rules/interactive-supports-focus.md): Enforce that elements with interactive handlers like `onClick` must be focusable.
110-
- [label-has-for](docs/rules/label-has-for.md): Enforce that `<label>` elements have the `htmlFor` prop.
110+
- [label-has-associated-control](docs/rules/label-has-associated-control.md): Enforce that a `label` tag has a text label and an associated control.
111111
- [lang](docs/rules/lang.md): Enforce lang attribute has a valid value.
112112
- [media-has-caption](docs/rules/media-has-caption.md): Enforces that `<audio>` and `<video>` elements must have a `<track>` for captions.
113113
- [mouse-events-have-key-events](docs/rules/mouse-events-have-key-events.md): Enforce that `onMouseOver`/`onMouseOut` are accompanied by `onFocus`/`onBlur` for keyboard-only users.
@@ -145,7 +145,7 @@ Rule | Recommended | Strict
145145
[iframe-has-title](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/iframe-has-title.md) | error | error
146146
[img-redundant-alt](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md) | error | error
147147
[interactive-supports-focus](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/interactive-supports-focus.md) | error | error
148-
[label-has-for](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-for.md) | error | error
148+
[label-has-associated-control](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md) | error | error
149149
[media-has-caption](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/media-has-caption.md) | error | error
150150
[mouse-events-have-key-events](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/mouse-events-have-key-events.md) | error | error
151151
[no-access-key](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md) | error | error
@@ -265,17 +265,17 @@ $ ./scripts/create-rule.js my-new-rule
265265
### Accessibility API
266266
An operating system will provide an accessibility API that maps application state and content onto input/output controllers such as a screen reader, braille device, keyboard, etc.
267267

268-
These APIs were developed as computer interfaces shifted from buffers (which are text based and inherently quite accessible) to graphical user interfaces (GUIs). The first attempts to make GUIs accessible involved raster image parsing to recognize characters, words, etc. This information was stored in a parallel buffer and made accessible to assistive technology (AT) devices.
268+
These APIs were developed as computer interfaces shifted from buffers (which are text-based and inherently quite accessible) to graphical user interfaces (GUIs). The first attempts to make GUIs accessible involved raster image parsing to recognize characters, words, etc. This information was stored in a parallel buffer and made accessible to assistive technology (AT) devices.
269269

270270
As GUIs became more complex, the raster parsing approach became untenable. Accessibility APIs were developed to replace them. Check out [NSAccessibility (AXAPI)](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSAccessibility_Protocol/index.html) for an example. See [Core Accessibility API Mappings 1.1](https://www.w3.org/TR/core-aam-1.1/) for more details.
271271

272272
### Browsers
273-
Browsers support an Accessibility API on a per operating system basis. For instance Firefox implements the MSAA accessibility API on Windows, but does not implement the AXAPI on OSX.
273+
Browsers support an Accessibility API on a per operating system basis. For instance, Firefox implements the MSAA accessibility API on Windows, but does not implement the AXAPI on OSX.
274274

275275
### The Accessibility (AX) Tree & DOM
276276
From the [W3 Core Accessibility API Mappings 1.1](https://www.w3.org/TR/core-aam-1.1/#intro_treetypes)
277277

278-
> The accessibility tree and the DOM tree are parallel structures. Roughly speaking the accessibility tree is a subset of the DOM tree. It includes the user interface objects of the user agent and the objects of the document. Accessible objects are created in the accessibility tree for every DOM element that should be exposed to an assistive technology, either because it may fire an accessibility event or because it has a property, relationship or feature which needs to be exposed. Generally if something can be trimmed out it will be, for reasons of performance and simplicity. For example, a <span> with just a style change and no semantics may not get its own accessible object, but the style change will be exposed by other means.
278+
> The accessibility tree and the DOM tree are parallel structures. Roughly speaking the accessibility tree is a subset of the DOM tree. It includes the user interface objects of the user agent and the objects of the document. Accessible objects are created in the accessibility tree for every DOM element that should be exposed to assistive technology, either because it may fire an accessibility event or because it has a property, relationship or feature which needs to be exposed. Generally, if something can be trimmed out it will be, for reasons of performance and simplicity. For example, a <span> with just a style change and no semantics may not get its own accessible object, but the style change will be exposed by other means.
279279
280280
Browser vendors are beginning to expose the AX Tree through inspection tools. Chrome has an experiment available to enable their inspection tool.
281281

@@ -291,8 +291,8 @@ You can also see a text-based version of the AX Tree in Chrome in the stable rel
291291
### Pulling it all together
292292
A browser constructs an AX Tree as a subset of the DOM. ARIA heavily informs the properties of this AX Tree. This AX Tree is exposed to the system level Accessibility API which mediates assistive technology agents.
293293

294-
We model ARIA in the [aria-query](https://github.com/a11yance/aria-query) project. We model AXObjects (that comprise the AX Tree) in the [axobject-query](https://github.com/A11yance/axobject-query) project. The goal of the WAI-ARIA specification is to be a complete declarative interface to the AXObject model. The [in-draft 1.2 version](https://github.com/w3c/aria/issues?q=is%3Aissue+is%3Aopen+label%3A%22ARIA+1.2%22) is moving towards this goal. But until then, we must consider the semantics constructs affored by ARIA as well as those afforded by the AXObject model (AXAPI) in order to determine how HTML can be used to express user interface affordances to assistive technology users.
294+
We model ARIA in the [aria-query](https://github.com/a11yance/aria-query) project. We model AXObjects (that comprise the AX Tree) in the [axobject-query](https://github.com/A11yance/axobject-query) project. The goal of the WAI-ARIA specification is to be a complete declarative interface to the AXObject model. The [in-draft 1.2 version](https://github.com/w3c/aria/issues?q=is%3Aissue+is%3Aopen+label%3A%22ARIA+1.2%22) is moving towards this goal. But until then, we must consider the semantics constructs afforded by ARIA as well as those afforded by the AXObject model (AXAPI) in order to determine how HTML can be used to express user interface affordances to assistive technology users.
295295

296296
## License
297297

298-
eslint-plugin-jsx-a11y is licensed under the [MIT License](LICENSE.md).
298+
eslint-plugin-jsx-a11y is licensed under the [MIT License](LICENSE.md).

__mocks__/JSXElementMock.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,24 @@
44

55
import JSXAttributeMock from './JSXAttributeMock';
66

7+
export type TJSXElementMock = {
8+
type: 'JSXElement',
9+
openingElement: {
10+
type: 'JSXOpeningElement',
11+
name: {
12+
type: 'JSXIdentifier',
13+
name: string,
14+
},
15+
attributes: Array<JSXAttributeMock>,
16+
},
17+
children: Array<Node>,
18+
};
19+
720
export default function JSXElementMock(
821
tagName: string,
922
attributes: Array<JSXAttributeMock> = [],
1023
children: Array<Node> = [],
11-
) {
24+
): TJSXElementMock {
1225
return {
1326
type: 'JSXElement',
1427
openingElement: {

__mocks__/genInteractives.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import includes from 'array-includes';
77
import JSXAttributeMock from './JSXAttributeMock';
88
import JSXElementMock from './JSXElementMock';
99

10+
import type { TJSXElementMock } from './JSXElementMock';
11+
1012
const domElements = [...dom.keys()];
1113
const roleNames = [...roles.keys()];
1214

@@ -149,8 +151,8 @@ export function genElementSymbol(openingElement: Object) {
149151
);
150152
}
151153

152-
export function genInteractiveElements() {
153-
return Object.keys(interactiveElementsMap).map((elementSymbol) => {
154+
export function genInteractiveElements(): Array<TJSXElementMock> {
155+
return Object.keys(interactiveElementsMap).map((elementSymbol: string): TJSXElementMock => {
154156
const bracketIndex = elementSymbol.indexOf('[');
155157
let name = elementSymbol;
156158
if (bracketIndex > -1) {
@@ -161,15 +163,15 @@ export function genInteractiveElements() {
161163
});
162164
}
163165

164-
export function genInteractiveRoleElements() {
165-
return [...interactiveRoles, 'button article', 'fakerole button article'].map(value => JSXElementMock(
166+
export function genInteractiveRoleElements(): Array<TJSXElementMock> {
167+
return [...interactiveRoles, 'button article', 'fakerole button article'].map((value): TJSXElementMock => JSXElementMock(
166168
'div',
167169
[JSXAttributeMock('role', value)],
168170
));
169171
}
170172

171-
export function genNonInteractiveElements() {
172-
return Object.keys(nonInteractiveElementsMap).map((elementSymbol) => {
173+
export function genNonInteractiveElements(): Array<TJSXElementMock> {
174+
return Object.keys(nonInteractiveElementsMap).map((elementSymbol): TJSXElementMock => {
173175
const bracketIndex = elementSymbol.indexOf('[');
174176
let name = elementSymbol;
175177
if (bracketIndex > -1) {
@@ -196,9 +198,9 @@ export function genNonAbstractRoleElements() {
196198
return nonAbstractRoles.map(value => JSXElementMock('div', [JSXAttributeMock('role', value)]));
197199
}
198200

199-
export function genIndeterminantInteractiveElements() {
201+
export function genIndeterminantInteractiveElements(): Array<TJSXElementMock> {
200202
return Object.keys(indeterminantInteractiveElementsMap).map((name) => {
201-
const attributes = indeterminantInteractiveElementsMap[name].map(({ prop, value }) => JSXAttributeMock(prop, value));
203+
const attributes = indeterminantInteractiveElementsMap[name].map(({ prop, value }): TJSXElementMock => JSXAttributeMock(prop, value));
202204
return JSXElementMock(name, attributes);
203205
});
204206
}

__tests__/__util__/parserOptionsMapper.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const defaultParserOptions = {
2-
ecmaVersion: 6,
2+
ecmaVersion: 2018,
33
ecmaFeatures: {
4+
experimentalObjectRestSpread: true,
45
jsx: true,
56
},
67
};

__tests__/src/rules/anchor-is-valid-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,15 @@ ruleTester.run('anchor-is-valid', rule, {
8989
{ code: '<a href="/foo" />' },
9090
{ code: '<a href="https://foo.bar.com" />' },
9191
{ code: '<div href="foo" />' },
92+
{ code: '<a href="javascript" />' },
93+
{ code: '<a href="javascriptFoo" />' },
9294
{ code: '<a href={`#foo`}/>' },
9395
{ code: '<a href={"foo"}/>' },
96+
{ code: '<a href={"javascript"}/>' },
97+
{ code: '<a href={`#javascript`}/>' },
9498
{ code: '<a href="#foo" />' },
99+
{ code: '<a href="#javascript" />' },
100+
{ code: '<a href="#javascriptFoo" />' },
95101
{ code: '<UX.Layout>test</UX.Layout>' },
96102
{ code: '<a href={this} />' },
97103

__tests__/src/rules/aria-proptypes-test.js

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,17 @@ ruleTester.run('aria-proptypes', rule, {
7474
{ code: '<div aria-hidden={!"yes"} />' },
7575
{ code: '<div aria-hidden={foo} />' },
7676
{ code: '<div aria-hidden={foo.bar} />' },
77+
{ code: '<div aria-hidden={null} />' },
78+
{ code: '<div aria-hidden={undefined} />' },
7779
{ code: '<div aria-hidden={<div />} />' },
7880

7981
// STRING
8082
{ code: '<div aria-label="Close" />' },
8183
{ code: '<div aria-label={`Close`} />' },
8284
{ code: '<div aria-label={foo} />' },
8385
{ code: '<div aria-label={foo.bar} />' },
86+
{ code: '<div aria-label={null} />' },
87+
{ code: '<div aria-label={undefined} />' },
8488
{ code: '<input aria-invalid={error ? "true" : "false"} />' },
8589
{ code: '<input aria-invalid={undefined ? "true" : "false"} />' },
8690

@@ -97,6 +101,8 @@ ruleTester.run('aria-proptypes', rule, {
97101
{ code: '<div aria-checked={foo.bar} />' },
98102
{ code: '<div aria-checked="mixed" />' },
99103
{ code: '<div aria-checked={`mixed`} />' },
104+
{ code: '<div aria-checked={null} />' },
105+
{ code: '<div aria-checked={undefined} />' },
100106

101107
// INTEGER
102108
{ code: '<div aria-level={123} />' },
@@ -108,6 +114,8 @@ ruleTester.run('aria-proptypes', rule, {
108114
{ code: '<div aria-level="123" />' },
109115
{ code: '<div aria-level={foo} />' },
110116
{ code: '<div aria-level={foo.bar} />' },
117+
{ code: '<div aria-level={null} />' },
118+
{ code: '<div aria-level={undefined} />' },
111119

112120
// NUMBER
113121
{ code: '<div aria-valuemax={123} />' },
@@ -119,6 +127,8 @@ ruleTester.run('aria-proptypes', rule, {
119127
{ code: '<div aria-valuemax="123" />' },
120128
{ code: '<div aria-valuemax={foo} />' },
121129
{ code: '<div aria-valuemax={foo.bar} />' },
130+
{ code: '<div aria-valuemax={null} />' },
131+
{ code: '<div aria-valuemax={undefined} />' },
122132

123133
// TOKEN
124134
{ code: '<div aria-sort="ascending" />' },
@@ -142,6 +152,8 @@ ruleTester.run('aria-proptypes', rule, {
142152
{ code: '<div aria-invalid="false" />' },
143153
{ code: '<div aria-invalid="grammar" />' },
144154
{ code: '<div aria-invalid="spelling" />' },
155+
{ code: '<div aria-invalid={null} />' },
156+
{ code: '<div aria-invalid={undefined} />' },
145157

146158
// TOKENLIST
147159
{ code: '<div aria-relevant="additions" />' },
@@ -159,6 +171,8 @@ ruleTester.run('aria-proptypes', rule, {
159171
{ code: '<div aria-relevant={`removals additions text all`} />' },
160172
{ code: '<div aria-relevant={foo} />' },
161173
{ code: '<div aria-relevant={foo.bar} />' },
174+
{ code: '<div aria-relevant={null} />' },
175+
{ code: '<div aria-relevant={undefined} />' },
162176

163177
// ID
164178
{ code: '<div aria-activedescendant="ascending" />' },
@@ -176,6 +190,8 @@ ruleTester.run('aria-proptypes', rule, {
176190
{ code: '<div aria-activedescendant={`other`} />' },
177191
{ code: '<div aria-activedescendant={foo} />' },
178192
{ code: '<div aria-activedescendant={foo.bar} />' },
193+
{ code: '<div aria-activedescendant={null} />' },
194+
{ code: '<div aria-activedescendant={undefined} />' },
179195

180196
// IDLIST
181197
{ code: '<div aria-labelledby="additions" />' },
@@ -193,13 +209,11 @@ ruleTester.run('aria-proptypes', rule, {
193209
{ code: '<div aria-labelledby={`removals additions text all`} />' },
194210
{ code: '<div aria-labelledby={foo} />' },
195211
{ code: '<div aria-labelledby={foo.bar} />' },
212+
{ code: '<div aria-labelledby={null} />' },
213+
{ code: '<div aria-labelledby={undefined} />' },
196214
].map(parserOptionsMapper),
197215
invalid: [
198216
// BOOLEAN
199-
{
200-
code: '<div aria-hidden={undefined} />',
201-
errors: [errorMessage('aria-hidden')],
202-
},
203217
{ code: '<div aria-hidden="yes" />', errors: [errorMessage('aria-hidden')] },
204218
{ code: '<div aria-hidden="no" />', errors: [errorMessage('aria-hidden')] },
205219
{ code: '<div aria-hidden={1234} />', errors: [errorMessage('aria-hidden')] },
@@ -209,18 +223,13 @@ ruleTester.run('aria-proptypes', rule, {
209223
},
210224

211225
// STRING
212-
{ code: '<div aria-label={undefined} />', errors: [errorMessage('aria-label')] },
213226
{ code: '<div aria-label />', errors: [errorMessage('aria-label')] },
214227
{ code: '<div aria-label={true} />', errors: [errorMessage('aria-label')] },
215228
{ code: '<div aria-label={false} />', errors: [errorMessage('aria-label')] },
216229
{ code: '<div aria-label={1234} />', errors: [errorMessage('aria-label')] },
217230
{ code: '<div aria-label={!true} />', errors: [errorMessage('aria-label')] },
218231

219232
// TRISTATE
220-
{
221-
code: '<div aria-checked={undefined} />',
222-
errors: [errorMessage('aria-checked')],
223-
},
224233
{ code: '<div aria-checked="yes" />', errors: [errorMessage('aria-checked')] },
225234
{ code: '<div aria-checked="no" />', errors: [errorMessage('aria-checked')] },
226235
{ code: '<div aria-checked={1234} />', errors: [errorMessage('aria-checked')] },
@@ -230,7 +239,6 @@ ruleTester.run('aria-proptypes', rule, {
230239
},
231240

232241
// INTEGER
233-
{ code: '<div aria-level={undefined} />', errors: [errorMessage('aria-level')] },
234242
{ code: '<div aria-level="yes" />', errors: [errorMessage('aria-level')] },
235243
{ code: '<div aria-level="no" />', errors: [errorMessage('aria-level')] },
236244
{ code: '<div aria-level={`abc`} />', errors: [errorMessage('aria-level')] },
@@ -240,10 +248,6 @@ ruleTester.run('aria-proptypes', rule, {
240248
{ code: '<div aria-level={!"false"} />', errors: [errorMessage('aria-level')] },
241249

242250
// NUMBER
243-
{
244-
code: '<div aria-valuemax={undefined} />',
245-
errors: [errorMessage('aria-valuemax')],
246-
},
247251
{ code: '<div aria-valuemax="yes" />', errors: [errorMessage('aria-valuemax')] },
248252
{ code: '<div aria-valuemax="no" />', errors: [errorMessage('aria-valuemax')] },
249253
{
@@ -268,7 +272,6 @@ ruleTester.run('aria-proptypes', rule, {
268272
{ code: '<div aria-sort="" />', errors: [errorMessage('aria-sort')] },
269273
{ code: '<div aria-sort="descnding" />', errors: [errorMessage('aria-sort')] },
270274
{ code: '<div aria-sort />', errors: [errorMessage('aria-sort')] },
271-
{ code: '<div aria-sort={undefined} />', errors: [errorMessage('aria-sort')] },
272275
{ code: '<div aria-sort={true} />', errors: [errorMessage('aria-sort')] },
273276
{ code: '<div aria-sort={"false"} />', errors: [errorMessage('aria-sort')] },
274277
{
@@ -283,10 +286,6 @@ ruleTester.run('aria-proptypes', rule, {
283286
errors: [errorMessage('aria-relevant')],
284287
},
285288
{ code: '<div aria-relevant />', errors: [errorMessage('aria-relevant')] },
286-
{
287-
code: '<div aria-relevant={undefined} />',
288-
errors: [errorMessage('aria-relevant')],
289-
},
290289
{
291290
code: '<div aria-relevant={true} />',
292291
errors: [errorMessage('aria-relevant')],

__tests__/src/rules/control-has-associated-label-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ const alwaysValid = [
141141
{ code: '<img />' },
142142
{ code: '<legend />' },
143143
{ code: '<li />' },
144+
{ code: '<link />' },
144145
{ code: '<main />' },
145146
{ code: '<mark />' },
146147
{ code: '<marquee />' },
@@ -254,7 +255,6 @@ const neverValid = [
254255
{ code: '<a href="#" />', errors: [expectedError] },
255256
{ code: '<area href="#" />', errors: [expectedError] },
256257
{ code: '<label />', errors: [expectedError] },
257-
{ code: '<link />', errors: [expectedError] },
258258
{ code: '<menuitem />', errors: [expectedError] },
259259
{ code: '<option />', errors: [expectedError] },
260260
{ code: '<th />', errors: [expectedError] },

__tests__/src/rules/no-access-key-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ ruleTester.run('no-access-key', rule, {
2828
{ code: '<div />;' },
2929
{ code: '<div {...props} />' },
3030
{ code: '<div accessKey={undefined} />' },
31-
{ code: '<div accessKey={`${undefined}`} />' },
32-
{ code: '<div accessKey={`${undefined}${undefined}`} />' },
3331
].map(parserOptionsMapper),
3432
invalid: [
3533
{ code: '<div accesskey="h" />', errors: [expectedError] },
@@ -44,5 +42,7 @@ ruleTester.run('no-access-key', rule, {
4442
},
4543
{ code: '<div accessKey={`This is ${bad}`} />', errors: [expectedError] },
4644
{ code: '<div accessKey={accessKey} />', errors: [expectedError] },
45+
{ code: '<div accessKey={`${undefined}`} />', errors: [expectedError] },
46+
{ code: '<div accessKey={`${undefined}${undefined}`} />', errors: [expectedError] },
4747
].map(parserOptionsMapper),
4848
});

0 commit comments

Comments
 (0)