Skip to content

Commit 9eeebaf

Browse files
kendallgassnerljharb
authored andcommitted
[New] add setting for polymorphic components
1 parent 7b3492b commit 9eeebaf

File tree

4 files changed

+65
-9
lines changed

4 files changed

+65
-9
lines changed

README.md

+20-3
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ Add `plugin:jsx-a11y/recommended` or `plugin:jsx-a11y/strict` in `extends`:
8989
}
9090
```
9191

92-
> As you are extending our configuration, you can omit `"plugins": ["jsx-a11y"]` from your `.eslintrc` configuration file.
92+
### Configurations
9393

94-
To enable your custom components to be checked as DOM elements, you can set global settings in your
95-
configuration file by mapping each custom component name to a DOM element type.
94+
> As you are extending our configuration, you can omit `"plugins": ["jsx-a11y"]` from your `.eslintrc` configuration file.
9695
9796
```json
9897
{
9998
"settings": {
10099
"jsx-a11y": {
100+
"polymorphicPropName": "as",
101101
"components": {
102102
"CityInput": "input",
103103
"CustomButton": "button",
@@ -109,6 +109,23 @@ configuration file by mapping each custom component name to a DOM element type.
109109
}
110110
```
111111

112+
#### Component Mapping
113+
114+
To enable your custom components to be checked as DOM elements, you can set global settings in your configuration file by mapping each custom component name to a DOM element type.
115+
116+
#### Polymorphic Components
117+
118+
You can optionally use the `polymorphicPropName` setting to define the prop your code uses to create polymorphic components.
119+
This setting will be used determine the element type in rules that require semantic context.
120+
121+
For example, if you set the `polymorphicPropName` setting to `as` then this element:
122+
123+
`<Box as="h3">Configurations </Box>`
124+
125+
will be evaluated as an `h3`. If no `polymorphicPropName` is set, then the component will be evaluated as `Box`.
126+
127+
⚠️ Polymorphic components can make code harder to maintain; please use this feature with caution.
128+
112129
## Supported Rules
113130

114131
<!-- begin auto-generated rules list -->

__tests__/src/util/getElementType-test.js

+33
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import expect from 'expect';
22
import getElementType from '../../../src/util/getElementType';
33
import JSXElementMock from '../../../__mocks__/JSXElementMock';
4+
import JSXAttributeMock from '../../../__mocks__/JSXAttributeMock';
45

56
describe('getElementType', () => {
67
describe('no settings in context', () => {
@@ -17,6 +18,9 @@ describe('getElementType', () => {
1718
it('should return the exact tag name for names that are in Object.prototype', () => {
1819
expect(elementType(JSXElementMock('toString').openingElement)).toBe('toString');
1920
});
21+
it('should return the default tag name provided', () => {
22+
expect(elementType(JSXElementMock('span', [JSXAttributeMock('as', 'h1')]).openingElement)).toBe('span');
23+
});
2024
});
2125

2226
describe('components settings in context', () => {
@@ -41,5 +45,34 @@ describe('getElementType', () => {
4145
it('should return the exact tag name for a custom element not in the components map', () => {
4246
expect(elementType(JSXElementMock('CityInput').openingElement)).toBe('CityInput');
4347
});
48+
49+
it('should return the default tag name since not polymorphicPropName was provided', () => {
50+
expect(elementType(JSXElementMock('span', [JSXAttributeMock('as', 'h1')]).openingElement)).toBe('span');
51+
});
52+
});
53+
54+
describe('polymorphicPropName settings in context', () => {
55+
const elementType = getElementType({
56+
settings: {
57+
'jsx-a11y': {
58+
polymorphicPropName: 'asChild',
59+
components: {
60+
CustomButton: 'button',
61+
},
62+
},
63+
},
64+
});
65+
66+
it('should return the tag name provided by the polymorphic prop, "asChild", defined in the settings', () => {
67+
expect(elementType(JSXElementMock('span', [JSXAttributeMock('asChild', 'h1')]).openingElement)).toBe('h1');
68+
});
69+
70+
it('should return the tag name provided by the polymorphic prop, "asChild", defined in the settings instead of the component mapping tag', () => {
71+
expect(elementType(JSXElementMock('CustomButton', [JSXAttributeMock('asChild', 'a')]).openingElement)).toBe('a');
72+
});
73+
74+
it('should return the tag name provided by the componnet mapping if the polymorphic prop, "asChild", defined in the settings is not set', () => {
75+
expect(elementType(JSXElementMock('CustomButton', [JSXAttributeMock('as', 'a')]).openingElement)).toBe('button');
76+
});
4477
});
4578
});

flow/eslint.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export type ESLintReport = {
99
export type ESLintSettings = {
1010
[string]: mixed,
1111
'jsx-a11y'?: {
12-
components: {[string]: string},
12+
polymorphicPropName?: string,
13+
components?: {[string]: string},
1314
},
1415
}
1516

src/util/getElementType.js

+10-5
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,23 @@
44

55
import type { JSXOpeningElement } from 'ast-types-flow';
66
import has from 'has';
7-
import { elementType } from 'jsx-ast-utils';
7+
import { elementType, getProp, getLiteralPropValue } from 'jsx-ast-utils';
88

99
import type { ESLintContext } from '../../flow/eslint';
1010

1111
const getElementType = (context: ESLintContext): ((node: JSXOpeningElement) => string) => {
1212
const { settings } = context;
13+
const polymorphicPropName = settings['jsx-a11y']?.polymorphicPropName;
1314
const componentMap = settings['jsx-a11y']?.components;
14-
if (!componentMap) {
15-
return elementType;
16-
}
15+
1716
return (node: JSXOpeningElement): string => {
18-
const rawType = elementType(node);
17+
const polymorphicProp = polymorphicPropName ? getLiteralPropValue(getProp(node.attributes, polymorphicPropName)) : undefined;
18+
const rawType = polymorphicProp ?? elementType(node);
19+
20+
if (!componentMap) {
21+
return rawType;
22+
}
23+
1924
return has(componentMap, rawType) ? componentMap[rawType] : rawType;
2025
};
2126
};

0 commit comments

Comments
 (0)