From c5c764929c03b21c6f33ba0dcbbb7f7beecadf6b Mon Sep 17 00:00:00 2001 From: michael faith Date: Fri, 20 Dec 2024 16:38:23 -0600 Subject: [PATCH] feat(label-has-associated-control): add option for enforcing label's htmlFor matches control's id This change adds an option to the `label-has-associated-control` rule, enforcing that the label's htmlFor attribute matches the associated control's id attribute. Previously, the only validation done on htmlFor was that it was on the label component and had text. There was no attempt to cross-check that value against any attribute on the associated control. Not, when the option is enabled, cases where they don't match will report. I also took the opportunity to update the error messages so that each assert type gets an error message with verbiage specific to the assertion. (not sure if this should be called out as a separate feature in the changelog?). Note: the current implementation only checks the first instance it finds of child component that matches each control component type. It assumes that there won't be any acceptable cases where a label would have multiple inputs nested beneath it. Let me know if that assumption doesn't hold. Closes: --- __mocks__/JSXFragmentMock.js | 17 ++ .../label-has-associated-control-test.js | 33 +++ __tests__/src/util/getChildComponent-test.js | 224 ++++++++++++++++++ __tests__/src/util/getParentElement-test.js | 177 ++++++++++++++ docs/rules/label-has-associated-control.md | 42 ++-- flow/eslint-jsx.js | 15 ++ flow/eslint.js | 6 + src/rules/label-has-associated-control.js | 116 ++++++++- src/util/getChildComponent.js | 52 ++++ src/util/getParentElement.js | 22 ++ src/util/mayContainChildComponent.js | 43 +--- 11 files changed, 684 insertions(+), 63 deletions(-) create mode 100644 __mocks__/JSXFragmentMock.js create mode 100644 __tests__/src/util/getChildComponent-test.js create mode 100644 __tests__/src/util/getParentElement-test.js create mode 100644 src/util/getChildComponent.js create mode 100644 src/util/getParentElement.js diff --git a/__mocks__/JSXFragmentMock.js b/__mocks__/JSXFragmentMock.js new file mode 100644 index 000000000..d126d5d1b --- /dev/null +++ b/__mocks__/JSXFragmentMock.js @@ -0,0 +1,17 @@ +/** + * @flow + */ + +export type JSXFragmentMockType = { + type: 'JSXFragment', + children: Array, +}; + +export default function JSXFragmentMock( + children?: Array = [], +): JSXFragmentMockType { + return { + type: 'JSXFragment', + children, + }; +} diff --git a/__tests__/src/rules/label-has-associated-control-test.js b/__tests__/src/rules/label-has-associated-control-test.js index 01d02e064..ad82c4249 100644 --- a/__tests__/src/rules/label-has-associated-control-test.js +++ b/__tests__/src/rules/label-has-associated-control-test.js @@ -27,6 +27,7 @@ const errorMessages = { nesting: 'A form label must have an associated control as a descendant.', either: 'A form label must either have a valid htmlFor attribute or a control as a descendant.', both: 'A form label must have a valid htmlFor attribute and a control as a descendant.', + htmlForShouldMatchId: 'A form label must have a htmlFor attribute that matches the id of the associated control.', }; const expectedErrors = {}; Object.keys(errorMessages).forEach((key) => { @@ -58,6 +59,7 @@ const htmlForValid = [ { code: '