Commit 2a98bada authored by David Sveningsson's avatar David Sveningsson

fix(rules): add contextual documentation for `element-name`

parent 1a896a87
Pipeline #124319278 passed with stages
in 11 minutes and 6 seconds
......@@ -10,7 +10,11 @@ Array [
"messages": Array [
Object {
"column": 2,
"context": undefined,
"context": Object {
"blacklist": Array [],
"pattern": "^[a-z][a-z0-9\\\\-._]*-[a-z0-9\\\\-._]*$",
"tagName": "foobar",
},
"line": 1,
"message": "<foobar> is not a valid element name",
"offset": 1,
......
......@@ -8,7 +8,11 @@ Array [
"messages": Array [
Object {
"column": 2,
"context": undefined,
"context": Object {
"blacklist": Array [],
"pattern": "^foo-\\\\w+$",
"tagName": "spam-ham",
},
"line": 27,
"message": "<spam-ham> is not a valid element name",
"offset": 319,
......@@ -19,7 +23,11 @@ Array [
},
Object {
"column": 2,
"context": undefined,
"context": Object {
"blacklist": Array [],
"pattern": "^foo-\\\\w+$",
"tagName": "foo",
},
"line": 30,
"message": "<foo> is not a valid element name",
"offset": 372,
......@@ -30,7 +38,11 @@ Array [
},
Object {
"column": 2,
"context": undefined,
"context": Object {
"blacklist": Array [],
"pattern": "^foo-\\\\w+$",
"tagName": "1-bar",
},
"line": 31,
"message": "<1-bar> is not a valid element name",
"offset": 384,
......@@ -85,7 +97,11 @@ Array [
"messages": Array [
Object {
"column": 2,
"context": undefined,
"context": Object {
"blacklist": Array [],
"pattern": "^[a-z][a-z0-9\\\\-._]*-[a-z0-9\\\\-._]*$",
"tagName": "foo",
},
"line": 30,
"message": "<foo> is not a valid element name",
"offset": 372,
......@@ -96,7 +112,11 @@ Array [
},
Object {
"column": 2,
"context": undefined,
"context": Object {
"blacklist": Array [],
"pattern": "^[a-z][a-z0-9\\\\-._]*-[a-z0-9\\\\-._]*$",
"tagName": "1-bar",
},
"line": 31,
"message": "<1-bar> is not a valid element name",
"offset": 384,
......@@ -143,9 +163,39 @@ Array [
]
`;
exports[`rule element-name should contain contextual documentation for blacklisted element 1`] = `
Object {
"description": "<element-name> is blacklisted by the project configuration.
The following names are blacklisted:
- element-name",
"url": "https://html-validate.org/rules/element-name.html",
}
`;
exports[`rule element-name should contain contextual documentation for element not matching custom pattern 1`] = `
Object {
"description": "<element-name> is not a valid element name. This project is configured to only allow names matching the following regular expression:
- \`^foo-.+$\`",
"url": "https://html-validate.org/rules/element-name.html",
}
`;
exports[`rule element-name should contain contextual documentation for element not matching default pattern 1`] = `
Object {
"description": "<element-name> is not a valid element name. If this is a custom element HTML requires the name to follow these rules:
- The name must begin with \`a-z\`
- The name must include a hyphen \`-\`
- It may include alphanumerical characters \`a-z0-9\` or hyphens \`-\`, dots \`.\` or underscores \`_\`.",
"url": "https://html-validate.org/rules/element-name.html",
}
`;
exports[`rule element-name should contain documentation 1`] = `
Object {
"description": "HTML defines what content is considered a valid (custom) element name.",
"description": "This is not a valid element name.",
"url": "https://html-validate.org/rules/element-name.html",
}
`;
import HtmlValidate from "../htmlvalidate";
import "../matchers";
const DEFAULT_PATTERN = "^[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*$";
describe("rule element-name", () => {
let htmlvalidate: HtmlValidate;
......@@ -121,4 +123,51 @@ describe("rule element-name", () => {
});
expect(htmlvalidate.getRuleDocumentation("element-name")).toMatchSnapshot();
});
describe("should contain contextual documentation for", () => {
it("blacklisted element", () => {
expect.assertions(1);
htmlvalidate = new HtmlValidate({
rules: { "element-name": "error" },
});
const context = {
tagName: "element-name",
pattern: DEFAULT_PATTERN,
blacklist: ["element-name"],
};
expect(
htmlvalidate.getRuleDocumentation("element-name", null, context)
).toMatchSnapshot();
});
it("element not matching default pattern", () => {
expect.assertions(1);
htmlvalidate = new HtmlValidate({
rules: { "element-name": "error" },
});
const context = {
tagName: "element-name",
pattern: DEFAULT_PATTERN,
blacklist: [] as string[],
};
expect(
htmlvalidate.getRuleDocumentation("element-name", null, context)
).toMatchSnapshot();
});
it("element not matching custom pattern", () => {
expect.assertions(1);
htmlvalidate = new HtmlValidate({
rules: { "element-name": "error" },
});
const context = {
tagName: "element-name",
pattern: "^foo-.+$",
blacklist: [] as string[],
};
expect(
htmlvalidate.getRuleDocumentation("element-name", null, context)
).toMatchSnapshot();
});
});
});
......@@ -2,6 +2,12 @@ import { sliceLocation } from "../context";
import { TagOpenEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
interface Context {
tagName: string;
pattern: string;
blacklist: string[];
}
interface RuleOptions {
pattern: string;
whitelist: string[];
......@@ -14,7 +20,7 @@ const defaults: RuleOptions = {
blacklist: [],
};
class ElementName extends Rule<void, RuleOptions> {
class ElementName extends Rule<Context, RuleOptions> {
private pattern: RegExp;
public constructor(options: RuleOptions) {
......@@ -24,24 +30,64 @@ class ElementName extends Rule<void, RuleOptions> {
this.pattern = new RegExp(this.options.pattern);
}
public documentation(): RuleDocumentation {
public documentation(context: Context): RuleDocumentation {
return {
description:
"HTML defines what content is considered a valid (custom) element name.",
description: this.documentationMessages(context).join("\n"),
url: ruleDocumentationUrl(__filename),
};
}
private documentationMessages(context: Context): string[] {
if (!context) {
return ["This is not a valid element name."];
}
if (context.blacklist.includes(context.tagName)) {
return [
`<${context.tagName}> is blacklisted by the project configuration.`,
"",
"The following names are blacklisted:",
...context.blacklist.map(cur => `- ${cur}`),
];
}
if (context.pattern !== defaults.pattern) {
return [
`<${context.tagName}> is not a valid element name. This project is configured to only allow names matching the following regular expression:`,
"",
`- \`${context.pattern}\``,
];
}
return [
`<${context.tagName}> is not a valid element name. If this is a custom element HTML requires the name to follow these rules:`,
"",
"- The name must begin with `a-z`",
"- The name must include a hyphen `-`",
"- It may include alphanumerical characters `a-z0-9` or hyphens `-`, dots `.` or underscores `_`.",
];
}
public setup(): void {
const xmlns = /^(.+):.+$/;
this.on("tag:open", (event: TagOpenEvent) => {
const target = event.target;
const tagName = target.tagName;
const location = sliceLocation(event.location, 1);
const context: Context = {
tagName,
pattern: this.options.pattern,
blacklist: this.options.blacklist,
};
/* check if element is blacklisted */
if (this.options.blacklist.indexOf(tagName) >= 0) {
this.report(target, `<${tagName}> element is blacklisted`, location);
if (this.options.blacklist.includes(tagName)) {
this.report(
target,
`<${tagName}> element is blacklisted`,
location,
context
);
}
/* assume that an element with meta has valid name as it is a builtin
......@@ -65,7 +111,8 @@ class ElementName extends Rule<void, RuleOptions> {
this.report(
target,
`<${tagName}> is not a valid element name`,
location
location,
context
);
}
});
......
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