diff --git a/__tests__/src/rules/label-has-associated-control-test.js b/__tests__/src/rules/label-has-associated-control-test.js
index 782fb112f..ec8002f80 100644
--- a/__tests__/src/rules/label-has-associated-control-test.js
+++ b/__tests__/src/rules/label-has-associated-control-test.js
@@ -36,6 +36,9 @@ const htmlForValid = [
{ code: '', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
// Custom label attributes.
{ code: '', options: [{ labelAttributes: ['label'] }] },
+ // Glob support for controlComponents option.
+ { code: '', options: [{ controlComponents: ['Custom*'] }] },
+ { code: '', options: [{ controlComponents: ['*Label'] }] },
];
const nestingValid = [
{ code: '' },
@@ -57,6 +60,9 @@ const nestingValid = [
{ code: '', options: [{ controlComponents: ['CustomInput'] }] },
{ code: 'A label', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }] },
{ code: '', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }] },
+ // Glob support for controlComponents option.
+ { code: '', options: [{ controlComponents: ['Custom*'] }] },
+ { code: '', options: [{ controlComponents: ['*Input'] }] },
];
const bothValid = [
diff --git a/__tests__/src/util/mayContainChildComponent-test.js b/__tests__/src/util/mayContainChildComponent-test.js
index b27eefde2..6cdd5ba41 100644
--- a/__tests__/src/util/mayContainChildComponent-test.js
+++ b/__tests__/src/util/mayContainChildComponent-test.js
@@ -104,4 +104,52 @@ describe('mayContainChildComponent', () => {
});
});
});
+
+ describe('Glob name matching', () => {
+ describe('component name contains question mark ? - match any single character', () => {
+ it('should return true', () => {
+ expect(mayContainChildComponent(
+ JSXElementMock('div', [], [
+ JSXElementMock('FancyComponent'),
+ ]),
+ 'Fanc?Co??onent',
+ )).toBe(true);
+ });
+ it('should return false', () => {
+ expect(mayContainChildComponent(
+ JSXElementMock('div', [], [
+ JSXElementMock('FancyComponent'),
+ ]),
+ 'FancyComponent?',
+ )).toBe(false);
+ });
+ });
+
+ describe('component name contains asterisk * - match zero or more characters', () => {
+ it('should return true', () => {
+ expect(mayContainChildComponent(
+ JSXElementMock('div', [], [
+ JSXElementMock('FancyComponent'),
+ ]),
+ 'Fancy*',
+ )).toBe(true);
+ });
+ it('should return true', () => {
+ expect(mayContainChildComponent(
+ JSXElementMock('div', [], [
+ JSXElementMock('FancyComponent'),
+ ]),
+ '*Component',
+ )).toBe(true);
+ });
+ it('should return true', () => {
+ expect(mayContainChildComponent(
+ JSXElementMock('div', [], [
+ JSXElementMock('FancyComponent'),
+ ]),
+ 'Fancy*C*t',
+ )).toBe(true);
+ });
+ });
+ });
});
diff --git a/docs/rules/label-has-associated-control.md b/docs/rules/label-has-associated-control.md
index 14e12c134..7037f07e1 100644
--- a/docs/rules/label-has-associated-control.md
+++ b/docs/rules/label-has-associated-control.md
@@ -101,7 +101,7 @@ This rule takes one optional object argument of type object:
`labelComponents` is a list of custom React Component names that should be checked for an associated control.
`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.
-`controlComponents` is a list of custom React Components names that will output an input element.
+`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`).
`assert` asserts that the label has htmlFor, a nested label, both or either. Available options: `'htmlFor', 'nesting', 'both', 'either'`.
`depth` (default 2, max 25) is an integer that determines how deep within a `JSXElement` label the rule should look for text content or an element with a label to determine if the `label` element will have an accessible label.
diff --git a/package.json b/package.json
index 2324a62e8..a73b987e7 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,8 @@
"emoji-regex": "^9.2.0",
"has": "^1.0.3",
"jsx-ast-utils": "^3.2.0",
- "language-tags": "^1.0.5"
+ "language-tags": "^1.0.5",
+ "minimatch": "^3.0.4"
},
"peerDependencies": {
"eslint": "^3 || ^4 || ^5 || ^6 || ^7"
diff --git a/src/util/mayContainChildComponent.js b/src/util/mayContainChildComponent.js
index bce434197..ddd98306f 100644
--- a/src/util/mayContainChildComponent.js
+++ b/src/util/mayContainChildComponent.js
@@ -9,6 +9,7 @@
import { elementType } from 'jsx-ast-utils';
import type { Node } from 'ast-types-flow';
+import minimatch from 'minimatch';
export default function mayContainChildComponent(
root: Node,
@@ -37,7 +38,7 @@ export default function mayContainChildComponent(
if (
childNode.type === 'JSXElement'
&& childNode.openingElement
- && elementType(childNode.openingElement) === componentName
+ && minimatch(elementType(childNode.openingElement), componentName)
) {
return true;
}