Skip to content

Commit da0ca32

Browse files
committed
[chore]: rename getAttributes to getSettingsAttributes
1 parent c5f68b9 commit da0ca32

File tree

5 files changed

+128
-141
lines changed

5 files changed

+128
-141
lines changed

__tests__/src/util/getAttributes-test.js

Lines changed: 0 additions & 98 deletions
This file was deleted.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import test from 'tape';
2+
3+
import getSettingsAttributes from '../../../src/util/getSettingsAttributes';
4+
import JSXElementMock from '../../../__mocks__/JSXElementMock';
5+
import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock';
6+
7+
test('getSettingsAttributes', (t) => {
8+
t.test('no settings in context', (st) => {
9+
st.deepEqual(
10+
getSettingsAttributes(JSXElementMock('a', [JSXAttributeMock('foo', 'path/to/page')]).openingElement, {}),
11+
[JSXAttributeMock('foo', 'path/to/page')],
12+
'returns existing attributes when no component settings exist',
13+
);
14+
15+
st.deepEqual(
16+
getSettingsAttributes(JSXElementMock('Link', [JSXAttributeMock('foo', 'path/to/page')]).openingElement, {}),
17+
[JSXAttributeMock('foo', 'path/to/page')],
18+
'returns existing attributes when no component settings exist',
19+
);
20+
21+
st.end();
22+
});
23+
24+
t.test('with component settings mapping', (st) => {
25+
st.deepEqual(
26+
getSettingsAttributes(JSXElementMock('Link', [JSXAttributeMock('foo', 'path/to/page')]).openingElement, {
27+
'jsx-a11y': {
28+
components: {
29+
Link: {
30+
component: 'a',
31+
attributes: {
32+
href: ['foo'],
33+
},
34+
},
35+
},
36+
},
37+
}),
38+
[JSXAttributeMock('href', 'path/to/page')],
39+
'returns the exisiting attributes and the mapped attributes',
40+
);
41+
42+
st.deepEqual(
43+
getSettingsAttributes(JSXElementMock('a', [JSXAttributeMock('foo', 'path/to/page')]).openingElement, {
44+
'jsx-a11y': {
45+
components: {
46+
Link: {
47+
component: 'a',
48+
attributes: {
49+
href: ['foo'],
50+
},
51+
},
52+
},
53+
},
54+
}),
55+
[JSXAttributeMock('foo', 'path/to/page')],
56+
'should return the existing attributes when no mapping exists',
57+
);
58+
59+
st.end();
60+
});
61+
62+
t.end();
63+
});

src/rules/no-static-element-interactions.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import isNonInteractiveElement from '../util/isNonInteractiveElement';
2727
import isNonInteractiveRole from '../util/isNonInteractiveRole';
2828
import isNonLiteralProperty from '../util/isNonLiteralProperty';
2929
import isPresentationRole from '../util/isPresentationRole';
30-
import getAttributes from '../util/getAttributes';
30+
import getSettingsAttributes from '../util/getSettingsAttributes';
3131

3232
const errorMessage = 'Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.';
3333

@@ -50,12 +50,13 @@ export default ({
5050
},
5151

5252
create: (context: ESLintContext): ESLintVisitorSelectorConfig => {
53-
const { options } = context;
53+
const { options, settings } = context;
5454
const elementType = getElementType(context);
5555
return {
5656
JSXOpeningElement: (node: JSXOpeningElement) => {
5757
const type = elementType(node);
58-
const attributes = getAttributes(node, type, context);
58+
// checking global settings for attribute mappings
59+
const attributes = getSettingsAttributes(node, settings);
5960

6061
const {
6162
allowExpressionValues,
@@ -64,8 +65,8 @@ export default ({
6465

6566
const hasInteractiveProps = handlers
6667
.some((prop) => (
67-
hasProp(attributes, prop)
68-
&& getPropValue(getProp(attributes, prop)) != null
68+
(hasProp(attributes, prop)
69+
&& getPropValue(getProp(attributes, prop)) != null)
6970
));
7071

7172
if (!dom.has(type)) {

src/util/getAttributes.js

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/util/getSettingsAttributes.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// @flow
2+
3+
import type { Node, JSXOpeningElement } from 'ast-types-flow';
4+
import { elementType, getProp } from 'jsx-ast-utils';
5+
import type { ESLintSettings } from '../../flow/eslint';
6+
7+
/**
8+
* Looks at the `settings` global config for custom component attribute mappings
9+
* This works for use cases where a custom component uses a prop name that renders to a specific attribute on the DOM element
10+
*
11+
* @example
12+
* {
13+
* 'jsx-a11y': {
14+
* components: {
15+
* Link: {
16+
* component: 'a',
17+
* attributes: {
18+
* href: ['foo'],
19+
* },
20+
* },
21+
* },
22+
* },
23+
* }
24+
*
25+
* <Link foo="path/to/page" /> // will be checked as if <a href="path/to/page" />
26+
*/
27+
const getSettingsAttributes = (node: JSXOpeningElement, settings: ESLintSettings): Node[] => {
28+
const { attributes: rawAttributes } = node;
29+
const { components } = settings?.['jsx-a11y'] || {};
30+
31+
if (!components || typeof components !== 'object') return rawAttributes;
32+
33+
const jsxElementName = elementType(node);
34+
const componentConfig = components[jsxElementName];
35+
36+
const { attributes: settingsAttributes } = typeof componentConfig === 'object' ? componentConfig : {};
37+
38+
if (!settingsAttributes || typeof settingsAttributes !== 'object') return rawAttributes;
39+
40+
const mappedAttributes = Object.entries(settingsAttributes).flatMap(([originalAttr, mappedAttrs]) => {
41+
if (!Array.isArray(mappedAttrs)) return [];
42+
43+
return mappedAttrs.flatMap((mappedAttr) => {
44+
const originalProp = getProp(rawAttributes, mappedAttr);
45+
46+
return originalProp ? [{
47+
...originalProp,
48+
name: {
49+
...originalProp.name,
50+
name: originalAttr,
51+
},
52+
}] : [];
53+
});
54+
});
55+
56+
return mappedAttributes;
57+
};
58+
59+
export default getSettingsAttributes;

0 commit comments

Comments
 (0)