1import { ConfigData, formatterFactory, HtmlValidate, Report, Reporter } from "html-validate";
2import "lume/types.ts";
3import { merge } from "lume/core/utils/object.ts";
4import { log } from "lume/core/utils/log.ts";
5
6// HTML Validation Plugin, by dish
7// version 1.0.1
8
9export const defaults: ConfigData = {
10 extends: ["html-validate:recommended", "html-validate:document"],
11 rules: {
12 "doctype-style": "off",
13 "attr-quotes": "off",
14 "no-trailing-whitespace": "off",
15 "void-style": "warn",
16 "require-sri": ["off", { target: "crossorigin" }],
17 },
18};
19
20export default function (userOptions?: ConfigData) {
21 const options = merge(defaults, userOptions);
22 const htmlvalidate = new HtmlValidate(options);
23
24 return (site: Lume.Site) => {
25 site.process([".html"], validatePages);
26 const barReport = site.debugBar?.collection("HTML Validator");
27 if (barReport) {
28 barReport.icon = "shield-check";
29 barReport.contexts = {
30 "error": {
31 background: "error",
32 },
33 "warning": {
34 background: "warning",
35 },
36 "success": {
37 background: "success",
38 },
39 };
40 }
41
42 async function validatePages(pages: Lume.Page[]) {
43 for (const page of pages) {
44 const report = await htmlvalidate.validateString(page.content as string, page.outputPath);
45 if (report.valid == true) {
46 barReport?.items.push({
47 title: page.data.url,
48 context: "success",
49 text: "No errors or warnings on page",
50 });
51 } else {
52 barReport?.items.push({
53 title: page.data.url,
54 details: `${report.errorCount} errors, ${report.warningCount} warnings`,
55 items: Array.from(report.results[0].messages).map((msg) => ({
56 title: msg.message,
57 context: severity(msg.severity),
58 code: msg.ruleId,
59 actions: [
60 {
61 text: "Rule",
62 icon: "question",
63 href: msg.ruleUrl,
64 target: "_blank",
65 },
66 ],
67 })),
68 });
69 }
70 }
71 }
72 };
73}
74
75function severity(level: number) {
76 if (level == 1) {
77 return "warning";
78 }
79
80 return "error";
81}