Skip to content

Commit 6cd1a70

Browse files
khiga8ljharb
authored andcommitted
[New] allow polymorphic linting to be restricted
This changes allows the consumer to restrict polymorphic linting to specified components. Linting components may raise false positives when a component handles behavior that the linter has no way to know. This means that linting components is preferred on very basic utility components.
1 parent a1ee7f8 commit 6cd1a70

File tree

4 files changed

+59
-2
lines changed

4 files changed

+59
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ For example, if you set the `polymorphicPropName` setting to `as` then this elem
221221

222222
will be evaluated as an `h3`. If no `polymorphicPropName` is set, then the component will be evaluated as `Box`.
223223

224+
To restrict polymorphic linting to specified components, additionally set `polymorphicAllowList` to an array of component names.
225+
224226
⚠️ Polymorphic components can make code harder to maintain; please use this feature with caution.
225227

226228
## Supported Rules

__tests__/src/util/getElementType-test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,49 @@ test('getElementType', (t) => {
106106
st.end();
107107
});
108108

109+
t.test('polymorphicPropName settings and explicitly defined polymorphicAllowList in context', (st) => {
110+
const elementType = getElementType({
111+
settings: {
112+
'jsx-a11y': {
113+
polymorphicPropName: 'asChild',
114+
polymorphicAllowList: [
115+
'Box',
116+
'Icon',
117+
],
118+
components: {
119+
Box: 'div',
120+
Icon: 'svg',
121+
},
122+
},
123+
},
124+
});
125+
126+
st.equal(
127+
elementType(JSXElementMock('Spinner', [JSXAttributeMock('asChild', 'img')]).openingElement),
128+
'Spinner',
129+
'does not use the polymorphic prop if polymorphicAllowList is defined, but element is not part of polymorphicAllowList',
130+
);
131+
132+
st.equal(
133+
elementType(JSXElementMock('Icon', [JSXAttributeMock('asChild', 'img')]).openingElement),
134+
'img',
135+
'uses the polymorphic prop if it is in explicitly defined polymorphicAllowList',
136+
);
137+
138+
st.equal(
139+
elementType(JSXElementMock('Box', [JSXAttributeMock('asChild', 'span')]).openingElement),
140+
'span',
141+
'returns the tag name provided by the polymorphic prop, "asChild", defined in the settings instead of the component mapping tag',
142+
);
143+
144+
st.equal(
145+
elementType(JSXElementMock('Box', [JSXAttributeMock('as', 'a')]).openingElement),
146+
'div',
147+
'returns the tag name provided by the component mapping if the polymorphic prop, "asChild", defined in the settings is not set',
148+
);
149+
150+
st.end();
151+
});
152+
109153
t.end();
110154
});

flow/eslint.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ export type ESLintReport = {
99
export type ESLintSettings = {
1010
[string]: mixed,
1111
'jsx-a11y'?: {
12-
polymorphicPropName?: string,
1312
components?: { [string]: string },
1413
attributes?: { for?: string[] },
14+
polymorphicPropName?: string,
15+
polymorphicAllowList?: Array<string>,
1516
},
1617
}
1718

src/util/getElementType.js

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

55
import type { JSXOpeningElement } from 'ast-types-flow';
66
import hasOwn from 'hasown';
7+
import includes from 'array-includes';
78
import { elementType, getProp, getLiteralPropValue } from 'jsx-ast-utils';
89

910
import type { ESLintContext } from '../../flow/eslint';
1011

1112
const getElementType = (context: ESLintContext): ((node: JSXOpeningElement) => string) => {
1213
const { settings } = context;
1314
const polymorphicPropName = settings['jsx-a11y']?.polymorphicPropName;
15+
const polymorphicAllowList = settings['jsx-a11y']?.polymorphicAllowList;
16+
1417
const componentMap = settings['jsx-a11y']?.components;
1518

1619
return (node: JSXOpeningElement): string => {
1720
const polymorphicProp = polymorphicPropName ? getLiteralPropValue(getProp(node.attributes, polymorphicPropName)) : undefined;
18-
const rawType = polymorphicProp ?? elementType(node);
21+
22+
let rawType = elementType(node);
23+
if (
24+
polymorphicProp
25+
&& (!polymorphicAllowList || includes(polymorphicAllowList, rawType))
26+
) {
27+
rawType = polymorphicProp;
28+
}
1929

2030
if (!componentMap) {
2131
return rawType;

0 commit comments

Comments
 (0)