diff --git a/__tests__/src/rules/label-has-associated-control-test.js b/__tests__/src/rules/label-has-associated-control-test.js
index d0b4c3b3..0bd14107 100644
--- a/__tests__/src/rules/label-has-associated-control-test.js
+++ b/__tests__/src/rules/label-has-associated-control-test.js
@@ -49,6 +49,8 @@ const htmlForValid = [
// Glob support for controlComponents option.
{ code: '', options: [{ controlComponents: ['Custom*'] }] },
{ code: '', options: [{ controlComponents: ['*Label'] }] },
+ // Rule does not error if presence of accessible label cannot be determined
+ { code: '
' },
];
const nestingValid = [
{ code: '' },
@@ -74,6 +76,8 @@ const nestingValid = [
// Glob support for controlComponents option.
{ code: '', options: [{ controlComponents: ['Custom*'] }] },
{ code: '', options: [{ controlComponents: ['*Input'] }] },
+ // Rule does not error if presence of accessible label cannot be determined
+ { code: '' },
];
const bothValid = [
diff --git a/src/rules/control-has-associated-label.js b/src/rules/control-has-associated-label.js
index 512a5213..4a691741 100644
--- a/src/rules/control-has-associated-label.js
+++ b/src/rules/control-has-associated-label.js
@@ -101,6 +101,8 @@ export default ({
node,
recursionDepth,
labelAttributes,
+ elementType,
+ controlComponents,
);
}
diff --git a/src/rules/label-has-associated-control.js b/src/rules/label-has-associated-control.js
index 7ee98ef6..fd66f2cf 100644
--- a/src/rules/label-has-associated-control.js
+++ b/src/rules/label-has-associated-control.js
@@ -87,6 +87,8 @@ export default ({
node,
recursionDepth,
options.labelAttributes,
+ elementType,
+ controlComponents,
);
if (hasAccessibleLabel) {
diff --git a/src/util/mayHaveAccessibleLabel.js b/src/util/mayHaveAccessibleLabel.js
index 31acee95..186ef5e0 100644
--- a/src/util/mayHaveAccessibleLabel.js
+++ b/src/util/mayHaveAccessibleLabel.js
@@ -9,8 +9,9 @@
*/
import includes from 'array-includes';
-import { getPropValue, propName } from 'jsx-ast-utils';
+import { getPropValue, propName, elementType as rawElementType } from 'jsx-ast-utils';
import type { JSXOpeningElement, Node } from 'ast-types-flow';
+import minimatch from 'minimatch';
function tryTrim(value: any) {
return typeof value === 'string' ? value.trim() : value;
@@ -46,6 +47,8 @@ export default function mayHaveAccessibleLabel(
root: Node,
maxDepth: number = 1,
additionalLabellingProps?: Array = [],
+ getElementType: ((node: JSXOpeningElement) => string) = rawElementType,
+ controlComponents: Array = [],
): boolean {
function checkElement(
node: Node,
@@ -77,6 +80,20 @@ export default function mayHaveAccessibleLabel(
) {
return true;
}
+
+ if (node.type === 'JSXElement' && node.children.length === 0 && node.openingElement) {
+ // $FlowFixMe `node.openingElement` has `unknown` type
+ const name = getElementType(node.openingElement);
+ const isReactComponent = name.length > 0 && name[0] === name[0].toUpperCase();
+
+ if (
+ isReactComponent
+ && !controlComponents.some((control) => minimatch(name, control))
+ ) {
+ return true;
+ }
+ }
+
// Recurse into the child element nodes.
if (node.children) {
/* $FlowFixMe */