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

fix(rules): handle id with whitespace in `no-redundant-for`

fixes #128
parent be9ea463
......@@ -126,3 +126,5 @@ Array [
},
]
`;
exports[`regression tests test-files/issues/issue128-id-whitespace.html 1`] = `Array []`;
......@@ -6,7 +6,7 @@ import { DOMNode } from "./domnode";
import { DOMTokenList } from "./domtokenlist";
import { DynamicValue } from "./dynamic-value";
import { NodeType } from "./nodetype";
import { Selector } from "./selector";
import { escapeSelectorComponent, Selector } from "./selector";
import { TextNode } from "./text";
export enum NodeClosed {
......@@ -153,7 +153,7 @@ export class HtmlElement extends DOMNode {
for (let cur: HtmlElement = this; cur.parent; cur = cur.parent) {
/* if a unique id is present, use it and short-circuit */
if (cur.id) {
const escaped = cur.id.replace(/([:[\] ])/g, "\\$1");
const escaped = escapeSelectorComponent(cur.id);
const matches = root.querySelectorAll(`#${escaped}`);
if (matches.length === 1) {
parts.push(`#${escaped}`);
......
......@@ -2,7 +2,7 @@ import { Config } from "../config";
import { Parser } from "../parser";
import { reset as resetDOMCounter } from "./domnode";
import { HtmlElement } from "./htmlelement";
import { Selector } from "./selector";
import { escapeSelectorComponent, Selector } from "./selector";
import { NodeType } from "./nodetype";
interface StrippedHtmlElement {
......@@ -31,6 +31,28 @@ function fetch(it: IterableIterator<HtmlElement>): StrippedHtmlElement[] {
return Array.from(it, stripHtmlElement);
}
describe("escapeSelectorComponent", () => {
it("should escape whitespace", () => {
expect.assertions(1);
expect(escapeSelectorComponent("foo bar")).toEqual("foo\\ bar");
});
it("should escape colon", () => {
expect.assertions(1);
expect(escapeSelectorComponent("foo:bar")).toEqual("foo\\:bar");
});
it("should escape square brackets", () => {
expect.assertions(1);
expect(escapeSelectorComponent("foo[bar]")).toEqual("foo\\[bar\\]");
});
it("should escape all occurrences", () => {
expect.assertions(1);
expect(escapeSelectorComponent("foo bar baz")).toEqual("foo\\ bar\\ baz");
});
});
describe("Selector", () => {
let doc: HtmlElement;
......
import { Attribute } from "./attribute";
import { Combinator, parseCombinator } from "./combinator";
import { DynamicValue } from "./dynamic-value";
import { HtmlElement } from "./htmlelement";
import { factory as pseudoClassFunction } from "./pseudoclass";
import { SelectorContext } from "./selector-context";
......@@ -13,6 +14,10 @@ function stripslashes(value: string): string {
return value.replace(/\\(.)/g, "$1");
}
export function escapeSelectorComponent(text: string | DynamicValue): string {
return text.toString().replace(/([:[\] ])/g, "\\$1");
}
abstract class Matcher {
/**
* Returns `true` if given node matches.
......
......@@ -22,6 +22,24 @@ describe("rule no-redundant-for", () => {
expect(report).toBeValid();
});
it("should handle omitted value", () => {
expect.assertions(1);
const report = htmlvalidate.validateString("<label for></label>");
expect(report).toBeValid();
});
it("should handle empty value", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<label for=""></label>');
expect(report).toBeValid();
});
it("should handle whitespace", () => {
expect.assertions(1);
const report = htmlvalidate.validateString('<label for="a b"></label><input id="a b">');
expect(report).toBeValid();
});
it("should not report error for other elements", () => {
expect.assertions(1);
const report = htmlvalidate.validateString(
......
import { escapeSelectorComponent } from "../dom/selector";
import { ElementReadyEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
......@@ -24,9 +25,15 @@ export default class NoRedundantFor extends Rule {
return;
}
/* try to find labeled control */
/* ignore omitted/empty values */
const id = attr.value;
const control = target.querySelector(`[id="${id}"]`);
if (!id) {
return;
}
/* try to find labeled control */
const escaped = escapeSelectorComponent(id);
const control = target.querySelector(`[id="${escaped}"]`);
if (!control) {
return;
}
......
<label for="foo bar">lorem ipsum</label>
<input type="text" id="foo bar">
Supports Markdown
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