-
-
Notifications
You must be signed in to change notification settings - Fork 637
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[label-has-associated-control] regression - rule errors when a label does not directly have text, even if it has htmlFor #966
Comments
what’s your eslint config? |
Sorry forgot to mention - using the default config for the rule with no settings. |
then in that case, I'd assume If you change your settings so that CustomInput is treated as an input, I'd expect the rule to no longer warn. |
htmlFor
In that case, you have two conflicting mechanisms - nesting and for/ID. What do browsers do in that case? |
The code will only produce invalid HTML if you assume The rule also works fine if the function Foo() {
return (
<>
<label className={styles.linkLabel} htmlFor="1">
<CustomText>Text</CustomText>
</label>
<input id="1" />
</>
);
} So the rule does make some assumptions (i.e. there's no requirement that a component renders children). From what i can tell - there's also no way around this error either. There's no config option to tell the rule "hey this component renders text it's okay" - the only way to satisfy the rule is to satisfy this utility function's checks - i.e. nested no more than
To summarise the problem:
I would propose that the whole "has accessible label" check be moved to its own rule entirely - it seems very weird that a rule entitled "label has associated control" also enforces that the label has valid text too. |
Hmm, interesting problem. I agree that since a custom component may not render its children, that a custom element with a text child can't be statically assumed to render text - that's a bug in that utility, and it should be changed to not have that false assumption. I also agree that "label has associated control" should only be concerned with a label and a control, not whether either of those things is accessible. I'd appreciate PRs for either or both of those issues. |
Sorry for pinging this issue! We're just encountering issues with our CI/CD, and I wanted to link a similar issue for documentation. Still ruling out whether the issue is on our end |
Version 6.8.0 has false positives jsx-eslint/eslint-plugin-jsx-a11y#966 A form label must be associated w ith a control jsx-a11y/label-has-associated-cont rol
I've hit this problem with the following:
Seems like any non-text node children of labels can cause some issues here. |
@tswaters are you sure you're not configured to use both, ie, to require an id/for combo also? |
As far as I'm aware we're using recommended and the rule started giving false positives after an update. In the code where we hit false positives, id/for are defined for everything. Without the div wrapping the input in the example, it works fine.... think of this as reduced test case 😁 |
More details now that I'm back at work. Here's some sample code that raises the lint rule: <input
id={`${this.props.id}-time-picker`}
name={`${this.props.id}-time-picker`}
type="time"
placeholder={this.props.placeholder}
disabled={this.props.disabled || this.props.readOnly}
className={`form-control time-input ${this.props.disabled ? 'disabled' : ''}`}
onChange={this.handleTimeChangeFromInput}
value={this.state.value ? moment(this.state.value).format('HH:mm') : ''}
onBlur={this.handleBlurEventFromInput}
/>
<div className={`input-group-addon ${this.props.disabled ? 'disabled' : ''}`}>
<label htmlFor={`${this.props.id}-time-picker`}>
<span aria-hidden="true" className="rw-i rw-i-clock-o" />
</label>
</div> As a test, if I apply this diff, it doesn't raise the lint rule: diff --git a/eos-web/components/js/common/components/DateTimePicker.jsx b/eos-web/components/js/common/components/DateTimePicker.jsx
index 51f17c8f16..89bbe216c2 100644
--- a/DateTimePicker.jsx
+++ b/DateTimePicker.jsx
@@ -340,7 +340,7 @@ class DateTimePicker extends PureComponent {
/>
<div className={`input-group-addon ${this.props.disabled ? 'disabled' : ''}`}>
<label htmlFor={`${this.props.id}-time-picker`}>
- <span aria-hidden="true" className="rw-i rw-i-clock-o" />
+ This is just a text node, surely it won't work?
</label>
</div>
</div> So, this component was written in 2017 and it's been like that basically since it was written. We started linting it with a11y in ~2022-09-29 -- and between then and now, both are using v6.6.1 of this module. I did run an eslint 8.24.0 |
@tswaters ok so as I try to turn this into a test case, I see that it's erroring, with or without the div around the label. Indeed, visible text content inside the label is required, otherwise it's not an accessible label. I don't think this is a regression; I think this is a bug fix that unfortunately introduced a lint warning for you. |
I think what I mentioned here applies to their case as well:
|
True, thanks for the reminder. The entire point of this rule is "has accessible label", though - what's the point of a label element if there's no label text in it? |
I see two separate concerns here:
The two concerns are related to one another in the context of a11y - I.e. "an IMO (1) matches with the name of the rule but (2) doesn't. |
Separate from organizational purity, what's the benefit of checking only the first? like, why would anyone want a label without accessible content to be present? (and why would the a11y linting plugin have that rule) |
IMO, the rule should be catching "oops! I forgot to add htmlFor to this label" and maybe some very basic structural checks like "this input has a label in parentNodes somewhere" Correct me if I'm wrong, but when looking at just the AST, it wouldn't be possible to check what the underlying component actually renders? |
@tswaters you're totally correct, and if the children of the label have any custom (non-HTML) components, then we can't know for sure either way what they render (and thus shouldn't warn on them). In the case above, though, it looks like you only have the label for an icon, which is not accessible (because it relies on non-text affordances) - can you add a text label for the icon? |
Because it is not possible to validate (2) statically. To do so requires cross-file analysis. For example imagine you have the following: // mod1.js
export function Custom() {
return <>Accessible label</>;
}
// mod2.js
import {Custom} from './mod1';
export function Component() {
return (
<>
<label htmlFor="1">
<Custom />
</label>
<input id="1" />
</>
);
} This code is 100% valid and accessible - but the rule errors on it because it cannot determine that Such a check may be desirable in some cases - I.e. To enforce that a11y constraints are declared in some statically analysable way - but many codebases would not want this check also and would be okay with assumptions. It would be possible to add options to configure this behaviour - including turning it off. But at that point it does seem to be better as a separate rule entirely that can be opted-into. |
** looks closely ** Oh shoot, that component isn't accessible at all, is it.... I'll need to double check how it behaves tomorrow when I'm back at work. I think I'm going to need to add "aria-label" to the span element. I'm pretty sure I had legit false positives though, that was just the first one I copied out 😂 |
@bradzacher in your example, I agree the linter rule shouldn't be warning on it. There are plenty of cases it can statically know nothing is rendered, but there a great many like your example where it can't know, and thus shouldn't complain. |
Fixes jsx-eslint#966 The rule no longer errors if the existence of label text cannot be determined
##### [v6.10.0](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/CHANGELOG.md#v6100---2024-09-03) ##### Fixed - \[New] `label-has-associated-control`: add additional error message [`#1005`](jsx-eslint/eslint-plugin-jsx-a11y#1005) - \[Fix] `label-has-associated-control`: ignore undetermined label text [`#966`](jsx-eslint/eslint-plugin-jsx-a11y#966) ##### Commits - \[Tests] switch from jest to tape [`a284cbf`](jsx-eslint/eslint-plugin-jsx-a11y@a284cbf) - \[New] add eslint 9 support [`deac4fd`](jsx-eslint/eslint-plugin-jsx-a11y@deac4fd) - \[New] add `attributes` setting [`a1ee7f8`](jsx-eslint/eslint-plugin-jsx-a11y@a1ee7f8) - \[New] allow polymorphic linting to be restricted [`6cd1a70`](jsx-eslint/eslint-plugin-jsx-a11y@6cd1a70) - \[Tests] remove duplicate tests [`74d5dec`](jsx-eslint/eslint-plugin-jsx-a11y@74d5dec) - \[Dev Deps] update `@babel/cli`, `@babel/core`, `@babel/eslint-parser`, `@babel/plugin-transform-flow-strip-types` [`6eca235`](jsx-eslint/eslint-plugin-jsx-a11y@6eca235) - \[readme] remove deprecated travis ci badge; add github actions badge [`0be7ea9`](jsx-eslint/eslint-plugin-jsx-a11y@0be7ea9) - \[Tests] use `npm audit` instead of `aud` [`05a5e49`](jsx-eslint/eslint-plugin-jsx-a11y@05a5e49) - \[Deps] update `axobject-query` [`912e98c`](jsx-eslint/eslint-plugin-jsx-a11y@912e98c) - \[Deps] unpin `axobject-query` [`75147aa`](jsx-eslint/eslint-plugin-jsx-a11y@75147aa) - \[Deps] update `axe-core` [`27ff7cb`](jsx-eslint/eslint-plugin-jsx-a11y@27ff7cb) - \[readme] fix jsxA11y import name [`ce846e0`](jsx-eslint/eslint-plugin-jsx-a11y@ce846e0) - \[readme] fix typo in shareable config section in readme [`cca288b`](jsx-eslint/eslint-plugin-jsx-a11y@cca288b)
| datasource | package | from | to | | ---------- | ---------------------- | ----- | ------ | | npm | eslint-plugin-jsx-a11y | 6.9.0 | 6.10.0 | ## [v6.10.0](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/CHANGELOG.md#v6100---2024-09-03) ##### Fixed - \[New] `label-has-associated-control`: add additional error message [`#1005`](jsx-eslint/eslint-plugin-jsx-a11y#1005) - \[Fix] `label-has-associated-control`: ignore undetermined label text [`#966`](jsx-eslint/eslint-plugin-jsx-a11y#966) ##### Commits - \[Tests] switch from jest to tape [`a284cbf`](jsx-eslint/eslint-plugin-jsx-a11y@a284cbf) - \[New] add eslint 9 support [`deac4fd`](jsx-eslint/eslint-plugin-jsx-a11y@deac4fd) - \[New] add `attributes` setting [`a1ee7f8`](jsx-eslint/eslint-plugin-jsx-a11y@a1ee7f8) - \[New] allow polymorphic linting to be restricted [`6cd1a70`](jsx-eslint/eslint-plugin-jsx-a11y@6cd1a70) - \[Tests] remove duplicate tests [`74d5dec`](jsx-eslint/eslint-plugin-jsx-a11y@74d5dec) - \[Dev Deps] update `@babel/cli`, `@babel/core`, `@babel/eslint-parser`, `@babel/plugin-transform-flow-strip-types` [`6eca235`](jsx-eslint/eslint-plugin-jsx-a11y@6eca235) - \[readme] remove deprecated travis ci badge; add github actions badge [`0be7ea9`](jsx-eslint/eslint-plugin-jsx-a11y@0be7ea9) - \[Tests] use `npm audit` instead of `aud` [`05a5e49`](jsx-eslint/eslint-plugin-jsx-a11y@05a5e49) - \[Deps] update `axobject-query` [`912e98c`](jsx-eslint/eslint-plugin-jsx-a11y@912e98c) - \[Deps] unpin `axobject-query` [`75147aa`](jsx-eslint/eslint-plugin-jsx-a11y@75147aa) - \[Deps] update `axe-core` [`27ff7cb`](jsx-eslint/eslint-plugin-jsx-a11y@27ff7cb) - \[readme] fix jsxA11y import name [`ce846e0`](jsx-eslint/eslint-plugin-jsx-a11y@ce846e0) - \[readme] fix typo in shareable config section in readme [`cca288b`](jsx-eslint/eslint-plugin-jsx-a11y@cca288b)
In 6.7.1 this code was fine and did not error.
In 6.8.0 this code results in an error.
The text was updated successfully, but these errors were encountered: