Skip to content

Commit

Permalink
[New] label-has-associated-control: Add glob support
Browse files Browse the repository at this point in the history
Fixes #720.
  • Loading branch information
hallzac2 authored and ljharb committed Oct 8, 2020
1 parent 854da0c commit 7d5511d
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 3 deletions.
6 changes: 6 additions & 0 deletions __tests__/src/rules/label-has-associated-control-test.js
Expand Up @@ -36,6 +36,9 @@ const htmlForValid = [
{ code: '<CustomLabel htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'], labelComponents: ['CustomLabel'] }] },
// Custom label attributes.
{ code: '<label htmlFor="js_id" label="A label" />', options: [{ labelAttributes: ['label'] }] },
// Glob support for controlComponents option.
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ controlComponents: ['Custom*'] }] },
{ code: '<CustomLabel htmlFor="js_id" aria-label="A label" />', options: [{ controlComponents: ['*Label'] }] },
];
const nestingValid = [
{ code: '<label>A label<input /></label>' },
Expand All @@ -57,6 +60,9 @@ const nestingValid = [
{ code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['CustomInput'] }] },
{ code: '<CustomLabel><span>A label<CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'] }] },
{ code: '<CustomLabel><span label="A label"><CustomInput /></span></CustomLabel>', options: [{ controlComponents: ['CustomInput'], labelComponents: ['CustomLabel'], labelAttributes: ['label'] }] },
// Glob support for controlComponents option.
{ code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['Custom*'] }] },
{ code: '<label><span>A label<CustomInput /></span></label>', options: [{ controlComponents: ['*Input'] }] },
];

const bothValid = [
Expand Down
48 changes: 48 additions & 0 deletions __tests__/src/util/mayContainChildComponent-test.js
Expand Up @@ -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);
});
});
});
});
2 changes: 1 addition & 1 deletion docs/rules/label-has-associated-control.md
Expand Up @@ -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.

Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion src/util/mayContainChildComponent.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 7d5511d

Please sign in to comment.