GitLab Commit is coming up on August 3-4. Learn how to innovate together using GitLab, the DevOps platform. Register for free: gitlabcommitvirtual2021.com

Commit d4445bb1 authored by David Sveningsson's avatar David Sveningsson
Browse files

feat(rules): new rule `no-redundant-for`

fixes #87
parent fc20b941
Pipeline #180161072 passed with stages
in 9 minutes and 51 seconds
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`docs/rules/no-redundant-for.md inline validation: correct 1`] = `Array []`;
exports[`docs/rules/no-redundant-for.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 1,
"filePath": "inline",
"messages": Array [
Object {
"column": 8,
"context": undefined,
"line": 1,
"message": "Redundant \\"for\\" attribute",
"offset": 7,
"ruleId": "no-redundant-for",
"selector": "label",
"severity": 2,
"size": 3,
},
],
"source": "<label for=\\"foo\\">
<input type=\\"checkbox\\" id=\\"foo\\">
My fancy checkbox
</label>",
"warningCount": 0,
},
]
`;
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<label for="foo">
<input type="checkbox" id="foo">
My fancy checkbox
</label>`;
markup["correct"] = `<!-- without for attribute -->
<label>
<input type="checkbox">
My fancy checkbox
</label>
<!-- without wrapping -->
<input type="checkbox" id="foo">
<label for="foo">
My fancy checkbox
</label>`;
describe("docs/rules/no-redundant-for.md", () => {
it("inline validation: incorrect", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({"rules":{"no-redundant-for":"error"}});
const report = htmlvalidate.validateString(markup["incorrect"]);
expect(report.results).toMatchSnapshot();
});
it("inline validation: correct", () => {
expect.assertions(1);
const htmlvalidate = new HtmlValidate({"rules":{"no-redundant-for":"error"}});
const report = htmlvalidate.validateString(markup["correct"]);
expect(report.results).toMatchSnapshot();
});
});
---
docType: rule
name: no-redundant-for
summary: Disallow usage of redundant label for attributes
---
# Disallow usage of redundant label for attributes (`no-redundant-for`)
`<label>` can either use the `for` attribute to reference the labelable control or wrap it.
Doing both is redundant as the label already references the control.
## Rule details
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="no-redundant-for">
<label for="foo">
<input type="checkbox" id="foo">
My fancy checkbox
</label>
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="no-redundant-for">
<!-- without for attribute -->
<label>
<input type="checkbox">
My fancy checkbox
</label>
<!-- without wrapping -->
<input type="checkbox" id="foo">
<label for="foo">
My fancy checkbox
</label>
</validate>
......@@ -8,6 +8,7 @@ const config: ConfigData = {
"meta-refresh": "error",
"no-autoplay": ["error", { include: ["audio", "video"] }],
"no-dup-id": "error",
"no-redundant-for": "error",
"no-redundant-role": "error",
"prefer-native-element": "error",
"svg-focusable": "error",
......
......@@ -32,6 +32,7 @@ const config: ConfigData = {
"no-implicit-close": "error",
"no-inline-style": "error",
"no-raw-characters": "error",
"no-redundant-for": "error",
"no-redundant-role": "error",
"no-self-closing": "error",
"no-trailing-whitespace": "error",
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rule no-redundant-for should contain documentation 1`] = `
Object {
"description": "When the \`<label>\` element wraps the labelable control the \`for\` attribute is redundant and better left out.",
"url": "https://html-validate.org/rules/no-redundant-for.html",
}
`;
......@@ -35,6 +35,7 @@ import NoImplicitClose from "./no-implicit-close";
import NoInlineStyle from "./no-inline-style";
import NoMissingReferences from "./no-missing-references";
import NoRawCharacters from "./no-raw-characters";
import NoRedundantFor from "./no-redundant-for";
import NoRedundantRole from "./no-redundant-role";
import NoSelfClosing from "./no-self-closing";
import NoStyleTag from "./no-style-tag";
......@@ -90,6 +91,7 @@ const bundledRules: Record<string, RuleConstructor<any, any>> = {
"no-inline-style": NoInlineStyle,
"no-missing-references": NoMissingReferences,
"no-raw-characters": NoRawCharacters,
"no-redundant-for": NoRedundantFor,
"no-redundant-role": NoRedundantRole,
"no-self-closing": NoSelfClosing,
"no-style-tag": NoStyleTag,
......
import HtmlValidate from "../htmlvalidate";
import "../matchers";
describe("rule no-redundant-for", () => {
let htmlvalidate: HtmlValidate;
beforeAll(() => {
htmlvalidate = new HtmlValidate({
rules: { "no-redundant-for": "error" },
});
});
it("should not report when <label> does not have for attribute", () => {
expect.assertions(1);
const report = htmlvalidate.validateString("<label><input></label>");
expect(report).toBeValid();
});
it("should not report when <label> references control elsewhere in tree", () => {
expect.assertions(1);
const report = htmlvalidate.validateString(
'<label for="foo"></label><input id="foo">'
);
expect(report).toBeValid();
});
it("should report error when <label> references wrapped element", () => {
expect.assertions(2);
const report = htmlvalidate.validateString(
'<label for="foo"><input id="foo"></label>'
);
expect(report).toBeInvalid();
expect(report).toHaveError("no-redundant-for", 'Redundant "for" attribute');
});
it("should contain documentation", () => {
expect.assertions(1);
htmlvalidate = new HtmlValidate({
rules: { "no-redundant-for": ["error", { style: "auto" }] },
});
expect(
htmlvalidate.getRuleDocumentation("no-redundant-for")
).toMatchSnapshot();
});
});
import { ElementReadyEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
export default class NoRedundantFor extends Rule {
public documentation(): RuleDocumentation {
return {
description: `When the \`<label>\` element wraps the labelable control the \`for\` attribute is redundant and better left out.`,
url: ruleDocumentationUrl(__filename),
};
}
public setup(): void {
this.on("element:ready", (event: ElementReadyEvent) => {
const { target } = event;
/* ignore label without for or dynamic value */
const attr = target.getAttribute("for");
if (!attr || attr.isDynamic) {
return;
}
/* try to find labeled control */
const id = attr.value;
const control = target.querySelector(`[id="${id}"]`);
if (!control) {
return;
}
this.report(target, 'Redundant "for" attribute', attr.keyLocation);
});
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment