Skip to content

Commit 743168b

Browse files
lb-ljharb
authored andcommitted
[New] label-has-associated-control: allow labelComponents to contain globs
Add ability for `labelComponents` within the `label-has-associated-control` to use the same glob checking mechanism as `controlComponents`. - Ensure existing tests pass and update unit tests for new behaviour - Add extra tests for documented `???Foo` syntax for component glob matching - Update documentation to have appropriate examples for label/control glob usage Closes #972
1 parent d13725d commit 743168b

File tree

3 files changed

+12
-4
lines changed

3 files changed

+12
-4
lines changed

__tests__/src/rules/label-has-associated-control-test.js

+5
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ const htmlForValid = [
6161
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }] },
6262
{ code: '<CustomLabel htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
6363
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', settings: componentsSettings },
64+
{ code: '<MUILabel htmlFor="js_id" aria-label="A label" />', options: [{ labelComponents: ['*Label'] }] },
65+
{ code: '<LabelCustom htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['Label*'] }] },
6466
// Custom label attributes.
6567
{ code: '<label htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'] }] },
6668
// Glob support for controlComponents option.
@@ -94,6 +96,7 @@ const nestingValid = [
9496
// Glob support for controlComponents option.
9597
{ code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['Custom*'] }] },
9698
{ code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['*Input'] }] },
99+
{ code: '<label><span>A label<TextInput /></span></label>', options: [{ controlComponents: ['????Input'] }] },
97100
// Rule does not error if presence of accessible label cannot be determined
98101
{ code: '<label><CustomText /><input /></label>' },
99102
];
@@ -106,6 +109,7 @@ const bothValid = [
106109
// Custom label component.
107110
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label"><input /></CustomLabel>', options: [{ labelComponents: ['CustomLabel'] }] },
108111
{ code: '<CustomLabel htmlFor="js_id" label="A label"><input /></CustomLabel>', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
112+
{ code: '<CustomLabel htmlFor="js_id" label="A label"><input /></CustomLabel>', options: [{ labelAttributes: ['label'], labelComponents: ['*Label'] }] },
109113
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label"><input /></CustomLabel>', settings: componentsSettings },
110114
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label"><CustomInput /></CustomLabel>', settings: componentsSettings },
111115
// Custom label attributes.
@@ -160,6 +164,7 @@ const neverValid = [
160164
{ code: '<div><label>A label</label><input /></div>', errors: [expectedError] },
161165
// Custom label component.
162166
{ code: '<CustomLabel aria-label="A label" />', options: [{ labelComponents: ['CustomLabel'] }], errors: [expectedError] },
167+
{ code: '<MUILabel aria-label="A label" />', options: [{ labelComponents: ['???Label'] }], errors: [expectedError] },
163168
{ code: '<CustomLabel label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }], errors: [expectedError] },
164169
{ code: '<CustomLabel aria-label="A label" />', settings: componentsSettings, errors: [expectedError] },
165170
// Custom label attributes.

docs/rules/label-has-associated-control.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ This rule takes one optional object argument of type object:
103103
}
104104
```
105105

106-
`labelComponents` is a list of custom React Component names that should be checked for an associated control.
106+
`labelComponents` is a list of custom React Component names that should be checked for an associated control. [Glob format](https://linuxhint.com/bash_globbing_tutorial/) is also supported for specifying names (e.g., `Label*` matches `LabelComponent` but not `CustomLabel`, `????Label` matches `LinkLabel` but not `CustomLabel`).
107107

108108
`labelAttributes` is a list of attributes to check on the label component and its children for a label. Use this if you have a custom component that uses a string passed on a prop to render an HTML `label`, for example.
109109

110-
`controlComponents` is a list of custom React Components names that will output an input element. [Glob format](https://linuxhint.com/bash_globbing_tutorial/) is also supported for specifying names (e.g., `Label*` matches `LabelComponent` but not `CustomLabel`, `????Label` matches `LinkLabel` but not `CustomLabel`).
110+
`controlComponents` is a list of custom React Components names that will output an input element. [Glob format](https://linuxhint.com/bash_globbing_tutorial/) is also supported for specifying names (e.g., `Input*` matches `InputCustom` but not `CustomInput`, `????Input` matches `TextInput` but not `CustomInput`).
111111

112112
`assert` asserts that the label has htmlFor, a nested label, both or either. Available options: `'htmlFor', 'nesting', 'both', 'either'`.
113113

src/rules/label-has-associated-control.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import { hasProp, getProp, getPropValue } from 'jsx-ast-utils';
1313
import type { JSXElement } from 'ast-types-flow';
14+
import minimatch from 'minimatch';
1415
import { generateObjSchema, arraySchema } from '../util/schemas';
1516
import type { ESLintConfig, ESLintContext, ESLintVisitorSelectorConfig } from '../../flow/eslint';
1617
import getElementType from '../util/getElementType';
@@ -66,13 +67,15 @@ export default ({
6667
const options = context.options[0] || {};
6768
const labelComponents = options.labelComponents || [];
6869
const assertType = options.assert || 'either';
69-
const componentNames = ['label'].concat(labelComponents);
70+
const labelComponentNames = ['label'].concat(labelComponents);
7071
const elementType = getElementType(context);
7172

7273
const rule = (node: JSXElement) => {
73-
if (componentNames.indexOf(elementType(node.openingElement)) === -1) {
74+
const isLabelComponent = labelComponentNames.some((name) => minimatch(elementType(node.openingElement), name));
75+
if (!isLabelComponent) {
7476
return;
7577
}
78+
7679
const controlComponents = [
7780
'input',
7881
'meter',

0 commit comments

Comments
 (0)