Commit 962d0791 authored by David Sveningsson's avatar David Sveningsson

feat: `attribute-allowed-values` handle omitted values

parent 6b71db74
Pipeline #124058924 passed with stages
in 10 minutes and 27 seconds
......@@ -5,9 +5,27 @@ exports[`docs/rules/attribute-allowed-values.md inline validation: correct 1`] =
exports[`docs/rules/attribute-allowed-values.md inline validation: incorrect 1`] = `
Array [
Object {
"errorCount": 1,
"errorCount": 2,
"filePath": "inline",
"messages": Array [
Object {
"column": 4,
"context": Object {
"allowed": Array [
/\\.\\*/,
],
"attribute": "href",
"element": "a",
"value": "",
},
"line": 1,
"message": "Attribute \\"href\\" is missing value",
"offset": 3,
"ruleId": "attribute-allowed-values",
"selector": "a",
"severity": 2,
"size": 4,
},
Object {
"column": 14,
"context": Object {
......@@ -39,16 +57,17 @@ Array [
"element": "input",
"value": "foobar",
},
"line": 1,
"line": 2,
"message": "Attribute \\"type\\" has invalid value \\"foobar\\"",
"offset": 13,
"offset": 29,
"ruleId": "attribute-allowed-values",
"selector": "input",
"severity": 2,
"size": 6,
},
],
"source": "<input type=\\"foobar\\">",
"source": "<a href>...</a>
<input type=\\"foobar\\">",
"warningCount": 0,
},
]
......
import HtmlValidate from "../../../src/htmlvalidate";
const markup: { [key: string]: string } = {};
markup["incorrect"] = `<input type="foobar">`;
markup["correct"] = `<input type="text">`;
markup["incorrect"] = `<a href>...</a>
<input type="foobar">`;
markup["correct"] = `<a href="page.html">...</a>
<input type="text">`;
describe("docs/rules/attribute-allowed-values.md", () => {
it("inline validation: incorrect", () => {
......
......@@ -28,11 +28,13 @@ The requirements comes from the [element metadata](/usage/elements.html):
Examples of **incorrect** code for this rule:
<validate name="incorrect" rules="attribute-allowed-values">
<a href>...</a>
<input type="foobar">
</validate>
Examples of **correct** code for this rule:
<validate name="correct" rules="attribute-allowed-values">
<a href="page.html">...</a>
<input type="text">
</validate>
......@@ -169,13 +169,16 @@ An object with allowed attribute values.
With this metadata the attribute `"foo"` may only have the values `"bar"` or
`"foo"`.
- To allow empty values explicitly list `""`:
`"my-attr": ["", "value 1", "value 2"]`
- Boolean attributes must be set to an empty list `[]`:
`"my-attr": []`
Regular expressions can also be used, e.g `"/-?\\d+/"` to match numbers.
- To allow empty values explicitly list `""`:
- `"my-attr": ["", "value 1", "value 2"]`
- Boolean attributes must be set to an empty list `[]` or include `""`:
- `"my-attr": []`
- `"my-attr": [""]`
- To allow empty string `my-attr=""` but not omitted value `my-attr` use regexp:
- `"my-attr": ["/.*/"]`
This is used by the
[attribute-allowed-values](/rules/attribute-allowed-values.html) rule.
......
......@@ -3,7 +3,7 @@
exports[`HTML elements <a> invalid markup 1`] = `
Array [
Object {
"errorCount": 4,
"errorCount": 5,
"filePath": "test-files/elements/a-invalid.html",
"messages": Array [
Object {
......@@ -50,6 +50,24 @@ Array [
"severity": 2,
"size": 1,
},
Object {
"column": 4,
"context": Object {
"allowed": Array [
/\\.\\*/,
],
"attribute": "href",
"element": "a",
"value": "",
},
"line": 12,
"message": "Attribute \\"href\\" is missing value",
"offset": 341,
"ruleId": "attribute-allowed-values",
"selector": "a:nth-child(5)",
"severity": 2,
"size": 4,
},
],
"source": "<!-- should be transparent -->
<span><a><div>foo</div></a></span>
......@@ -60,6 +78,9 @@ Array [
<!-- should not allow nesting -->
<a><span><a>foo</a></span></a>
<!-- should not allow omitted href value -->
<a href>foo</a>
",
"warningCount": 0,
},
......@@ -3481,7 +3502,7 @@ Array [
"size": 4,
},
Object {
"column": 2,
"column": 21,
"context": Object {
"allowed": Array [
/\\.\\+/,
......@@ -3491,12 +3512,12 @@ Array [
"value": "",
},
"line": 9,
"message": "Attribute \\"integrity\\" has invalid value \\"\\"",
"offset": 182,
"message": "Attribute \\"integrity\\" is missing value",
"offset": 201,
"ruleId": "attribute-allowed-values",
"selector": "link:nth-child(4)",
"severity": 2,
"size": 4,
"size": 9,
},
Object {
"column": 2,
......@@ -3634,7 +3655,7 @@ Array [
"filePath": "test-files/elements/math-invalid.html",
"messages": Array [
Object {
"column": 2,
"column": 7,
"context": Object {
"allowed": Array [
"ltr",
......@@ -3648,12 +3669,12 @@ Array [
"value": "",
},
"line": 2,
"message": "Attribute \\"dir\\" has invalid value \\"\\"",
"offset": 32,
"message": "Attribute \\"dir\\" is missing value",
"offset": 37,
"ruleId": "attribute-allowed-values",
"selector": "math:nth-child(1)",
"severity": 2,
"size": 4,
"size": 3,
},
Object {
"column": 12,
......@@ -3678,7 +3699,7 @@ Array [
"size": 6,
},
Object {
"column": 2,
"column": 7,
"context": Object {
"allowed": Array [
"block",
......@@ -3689,12 +3710,12 @@ Array [
"value": "",
},
"line": 4,
"message": "Attribute \\"display\\" has invalid value \\"\\"",
"offset": 77,
"message": "Attribute \\"display\\" is missing value",
"offset": 82,
"ruleId": "attribute-allowed-values",
"selector": "math:nth-child(3)",
"severity": 2,
"size": 4,
"size": 7,
},
Object {
"column": 16,
......@@ -3716,7 +3737,7 @@ Array [
"size": 6,
},
Object {
"column": 2,
"column": 7,
"context": Object {
"allowed": Array [
"linebreak",
......@@ -3730,12 +3751,12 @@ Array [
"value": "",
},
"line": 6,
"message": "Attribute \\"overflow\\" has invalid value \\"\\"",
"offset": 130,
"message": "Attribute \\"overflow\\" is missing value",
"offset": 135,
"ruleId": "attribute-allowed-values",
"selector": "math:nth-child(5)",
"severity": 2,
"size": 4,
"size": 8,
},
Object {
"column": 17,
......@@ -4037,7 +4058,7 @@ Array [
"size": 8,
},
Object {
"column": 2,
"column": 9,
"context": Object {
"allowed": Array [
/\\.\\+/,
......@@ -4047,12 +4068,12 @@ Array [
"value": "",
},
"line": 7,
"message": "Attribute \\"integrity\\" has invalid value \\"\\"",
"offset": 131,
"message": "Attribute \\"integrity\\" is missing value",
"offset": 138,
"ruleId": "attribute-allowed-values",
"selector": "script:nth-child(2)",
"severity": 2,
"size": 6,
"size": 9,
},
Object {
"column": 2,
......
......@@ -26,7 +26,8 @@
"urn"
],
"attributes": {
"download": ["", "/.+/"]
"download": ["", "/.+/"],
"href": ["/.*/"]
},
"permittedDescendants": [{ "exclude": "@interactive" }]
},
......
......@@ -527,19 +527,26 @@ describe("Meta validator", () => {
).toBeTruthy();
});
it("should match regexp", () => {
const rules = {
foo: [/ba.*/],
};
expect(
Validator.validateAttribute(new Attribute("foo", "bar"), rules)
).toBeTruthy();
expect(
Validator.validateAttribute(new Attribute("foo", "car"), rules)
).toBeFalsy();
expect(
Validator.validateAttribute(new Attribute("foo", null), rules)
).toBeFalsy();
it.each`
regex | value
${/ba.*/} | ${"bar"}
${/.*/} | ${"foo"}
${/.*/} | ${""}
`("should match regexp $regex vs $value", ({ regex, value }) => {
const rules = { foo: [regex] };
const attr = new Attribute("foo", value);
expect(Validator.validateAttribute(attr, rules)).toBeTruthy();
});
it.each`
regex | value | expected
${/ba.*/} | ${"car"} | ${false}
${/ba.*/} | ${null} | ${false}
${/.*/} | ${null} | ${false}
`("should not match regexp $regex vs $value", ({ regex, value }) => {
const rules = { foo: [regex] };
const attr = new Attribute("foo", value);
expect(Validator.validateAttribute(attr, rules)).toBeFalsy();
});
it("should match string value", () => {
......@@ -580,6 +587,15 @@ describe("Meta validator", () => {
).toBeFalsy();
});
it("should handle null", () => {
const rules = {
foo: ["foo", "/bar/"],
};
expect(
Validator.validateAttribute(new Attribute("foo", null), rules)
).toBeFalsy();
});
it("should consider empty list as boolean attribute", () => {
const rules = {
foo: [] as string[],
......
......@@ -196,9 +196,13 @@ export class Validator {
return true;
}
if (value === null || value === undefined) {
return false;
}
return rule.some((entry: string | RegExp) => {
if (entry instanceof RegExp) {
return value && !!value.match(entry);
return !!value.match(entry);
} else {
return value === entry;
}
......
......@@ -25,7 +25,7 @@ describe("rule attribute-allowed-values", () => {
expect(report).toBeInvalid();
expect(report).toHaveError(
"attribute-allowed-values",
'Attribute "type" has invalid value ""'
'Attribute "type" is missing value'
);
});
......
import { HtmlElement } from "../dom";
import { Location } from "../context";
import { HtmlElement, Attribute } from "../dom";
import { DOMReadyEvent } from "../event";
import { Validator } from "../meta";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
......@@ -60,16 +61,30 @@ class AttributeAllowedValues extends Rule<Context> {
value,
allowed: meta.attributes[attr.key],
};
this.report(
node,
`Attribute "${attr.key}" has invalid value "${value}"`,
attr.valueLocation,
context
);
const message = this.getMessage(attr);
const location = this.getLocation(attr);
this.report(node, message, location, context);
}
});
});
}
private getMessage(attr: Attribute): string {
const { key, value } = attr;
if (value !== null) {
return `Attribute "${key}" has invalid value "${value.toString()}"`;
} else {
return `Attribute "${key}" is missing value`;
}
}
private getLocation(attr: Attribute): Location {
if (attr.value !== null) {
return attr.valueLocation;
} else {
return attr.keyLocation;
}
}
}
module.exports = AttributeAllowedValues;
......@@ -7,3 +7,6 @@
<!-- should not allow nesting -->
<a><span><a>foo</a></span></a>
<!-- should not allow omitted href value -->
<a href>foo</a>
......@@ -14,3 +14,7 @@
<a download>foo</a>
<a download="">foo</a>
<a download="foo.txtr">foo</a>
<!-- should allow empty or non-empty string as href (but not omitted) -->
<a href="">foo</a>
<a href="foo.html">foo</a>
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