From 6cd1a7011446e3925f2b49c51ff26246a21491d1 Mon Sep 17 00:00:00 2001 From: Kate Higa <16447748+khiga8@users.noreply.github.com> Date: Mon, 6 May 2024 09:03:46 -0400 Subject: [PATCH] [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. --- README.md | 2 ++ __tests__/src/util/getElementType-test.js | 44 +++++++++++++++++++++++ flow/eslint.js | 3 +- src/util/getElementType.js | 12 ++++++- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 283f0d39..e5119223 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__tests__/src/util/getElementType-test.js b/__tests__/src/util/getElementType-test.js index 97127687..81b6ff42 100644 --- a/__tests__/src/util/getElementType-test.js +++ b/__tests__/src/util/getElementType-test.js @@ -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(); }); diff --git a/flow/eslint.js b/flow/eslint.js index 670d1ab1..af299b6b 100644 --- a/flow/eslint.js +++ b/flow/eslint.js @@ -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, }, } diff --git a/src/util/getElementType.js b/src/util/getElementType.js index c735a805..1fdab721 100644 --- a/src/util/getElementType.js +++ b/src/util/getElementType.js @@ -4,6 +4,7 @@ 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'; @@ -11,11 +12,20 @@ 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;