Skip to content

Commit

Permalink
[New] allow polymorphic linting to be restricted
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
khiga8 authored and ljharb committed May 6, 2024
1 parent a1ee7f8 commit 6cd1a70
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ For example, if you set the `polymorphicPropName` setting to `as` then this elem

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

To restrict polymorphic linting to specified components, additionally set `polymorphicAllowList` to an array of component names.

⚠️ Polymorphic components can make code harder to maintain; please use this feature with caution.

## Supported Rules
Expand Down
44 changes: 44 additions & 0 deletions __tests__/src/util/getElementType-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,49 @@ test('getElementType', (t) => {
st.end();
});

t.test('polymorphicPropName settings and explicitly defined polymorphicAllowList in context', (st) => {
const elementType = getElementType({
settings: {
'jsx-a11y': {
polymorphicPropName: 'asChild',
polymorphicAllowList: [
'Box',
'Icon',
],
components: {
Box: 'div',
Icon: 'svg',
},
},
},
});

st.equal(
elementType(JSXElementMock('Spinner', [JSXAttributeMock('asChild', 'img')]).openingElement),
'Spinner',
'does not use the polymorphic prop if polymorphicAllowList is defined, but element is not part of polymorphicAllowList',
);

st.equal(
elementType(JSXElementMock('Icon', [JSXAttributeMock('asChild', 'img')]).openingElement),
'img',
'uses the polymorphic prop if it is in explicitly defined polymorphicAllowList',
);

st.equal(
elementType(JSXElementMock('Box', [JSXAttributeMock('asChild', 'span')]).openingElement),
'span',
'returns the tag name provided by the polymorphic prop, "asChild", defined in the settings instead of the component mapping tag',
);

st.equal(
elementType(JSXElementMock('Box', [JSXAttributeMock('as', 'a')]).openingElement),
'div',
'returns the tag name provided by the component mapping if the polymorphic prop, "asChild", defined in the settings is not set',
);

st.end();
});

t.end();
});
3 changes: 2 additions & 1 deletion flow/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export type ESLintReport = {
export type ESLintSettings = {
[string]: mixed,
'jsx-a11y'?: {
polymorphicPropName?: string,
components?: { [string]: string },
attributes?: { for?: string[] },
polymorphicPropName?: string,
polymorphicAllowList?: Array<string>,
},
}

Expand Down
12 changes: 11 additions & 1 deletion src/util/getElementType.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@

import type { JSXOpeningElement } from 'ast-types-flow';
import hasOwn from 'hasown';
import includes from 'array-includes';
import { elementType, getProp, getLiteralPropValue } from 'jsx-ast-utils';

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

const getElementType = (context: ESLintContext): ((node: JSXOpeningElement) => string) => {
const { settings } = context;
const polymorphicPropName = settings['jsx-a11y']?.polymorphicPropName;
const polymorphicAllowList = settings['jsx-a11y']?.polymorphicAllowList;

const componentMap = settings['jsx-a11y']?.components;

return (node: JSXOpeningElement): string => {
const polymorphicProp = polymorphicPropName ? getLiteralPropValue(getProp(node.attributes, polymorphicPropName)) : undefined;
const rawType = polymorphicProp ?? elementType(node);

let rawType = elementType(node);
if (
polymorphicProp
&& (!polymorphicAllowList || includes(polymorphicAllowList, rawType))
) {
rawType = polymorphicProp;
}

if (!componentMap) {
return rawType;
Expand Down

0 comments on commit 6cd1a70

Please sign in to comment.