@staticforge/checker 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +6 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +28 -0
- package/dist/cache.js.map +1 -0
- package/dist/check.d.ts +79 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +336 -0
- package/dist/check.js.map +1 -0
- package/dist/headless.d.ts +25 -0
- package/dist/headless.d.ts.map +1 -0
- package/dist/headless.js +209 -0
- package/dist/headless.js.map +1 -0
- package/dist/html-validate.d.ts +4 -0
- package/dist/html-validate.d.ts.map +1 -0
- package/dist/html-validate.js +103 -0
- package/dist/html-validate.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/output-rules.d.ts +25 -0
- package/dist/output-rules.d.ts.map +1 -0
- package/dist/output-rules.js +265 -0
- package/dist/output-rules.js.map +1 -0
- package/dist/report/index.d.ts +5 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +5 -0
- package/dist/report/index.js.map +1 -0
- package/dist/report/junit.d.ts +8 -0
- package/dist/report/junit.d.ts.map +1 -0
- package/dist/report/junit.js +51 -0
- package/dist/report/junit.js.map +1 -0
- package/dist/report/pretty.d.ts +9 -0
- package/dist/report/pretty.d.ts.map +1 -0
- package/dist/report/pretty.js +92 -0
- package/dist/report/pretty.js.map +1 -0
- package/dist/report/sarif.d.ts +14 -0
- package/dist/report/sarif.d.ts.map +1 -0
- package/dist/report/sarif.js +65 -0
- package/dist/report/sarif.js.map +1 -0
- package/dist/report/schema.d.ts +3 -0
- package/dist/report/schema.d.ts.map +1 -0
- package/dist/report/schema.js +57 -0
- package/dist/report/schema.js.map +1 -0
- package/dist/source-rules.d.ts +24 -0
- package/dist/source-rules.d.ts.map +1 -0
- package/dist/source-rules.js +327 -0
- package/dist/source-rules.js.map +1 -0
- package/dist/tasks.d.ts +36 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +30 -0
- package/dist/tasks.js.map +1 -0
- package/dist/text.d.ts +32 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +55 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -3
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { HtmlValidate, StaticConfigLoader } from 'html-validate';
|
|
2
|
+
/**
|
|
3
|
+
* html-validate rules that check the same thing as a staticforge rule report
|
|
4
|
+
* under the staticforge ID, so the finding is de-duplicated against (and fixed
|
|
5
|
+
* by) the source rule. Everything else is reported as `html/<rule>`.
|
|
6
|
+
*/
|
|
7
|
+
const ALIASES = {
|
|
8
|
+
'heading-level': 'heading-order',
|
|
9
|
+
'wcag/h37': 'img-alt',
|
|
10
|
+
};
|
|
11
|
+
/** WCAG success criteria for html-validate's accessibility rules. */
|
|
12
|
+
const WCAG = {
|
|
13
|
+
'area-alt': '1.1.1',
|
|
14
|
+
'aria-hidden-body': '4.1.2',
|
|
15
|
+
'aria-label-misuse': '4.1.2',
|
|
16
|
+
'empty-heading': '2.4.6',
|
|
17
|
+
'heading-level': '1.3.1',
|
|
18
|
+
'hidden-focusable': '4.1.2',
|
|
19
|
+
'input-missing-label': '1.3.1',
|
|
20
|
+
'meta-refresh': '2.2.1',
|
|
21
|
+
'multiple-labeled-controls': '1.3.1',
|
|
22
|
+
'no-autoplay': '1.4.2',
|
|
23
|
+
'no-redundant-role': '4.1.2',
|
|
24
|
+
'text-content': '4.1.2',
|
|
25
|
+
'unique-landmark': '1.3.1',
|
|
26
|
+
'valid-autocomplete': '1.3.5',
|
|
27
|
+
'wcag/h30': '2.4.4',
|
|
28
|
+
'wcag/h32': '3.2.2',
|
|
29
|
+
'wcag/h36': '1.1.1',
|
|
30
|
+
'wcag/h37': '1.1.1',
|
|
31
|
+
'wcag/h63': '1.3.1',
|
|
32
|
+
'wcag/h67': '1.1.1',
|
|
33
|
+
'wcag/h71': '1.3.1',
|
|
34
|
+
};
|
|
35
|
+
/** Messages that a staticforge rule reports (with a fix or a better hint): [rule, message text]. */
|
|
36
|
+
const COVERED = [
|
|
37
|
+
['element-required-content', '<title>'], // document-title
|
|
38
|
+
['element-required-attributes', '"lang"'], // html-lang
|
|
39
|
+
['element-required-attributes', '<iframe> is missing required "title"'], // iframe-title
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* html-validate resolves (and schema-validates) its config for every file,
|
|
43
|
+
* which took more than half of the time per page. One config applies to all
|
|
44
|
+
* pages, so it is resolved once.
|
|
45
|
+
*/
|
|
46
|
+
class ResolvedOnceLoader extends StaticConfigLoader {
|
|
47
|
+
resolved;
|
|
48
|
+
getConfigFor(handle, configOverride) {
|
|
49
|
+
if (configOverride !== undefined)
|
|
50
|
+
return super.getConfigFor(handle, configOverride);
|
|
51
|
+
this.resolved ??= super.getConfigFor(handle);
|
|
52
|
+
return this.resolved;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let validator;
|
|
56
|
+
function getValidator() {
|
|
57
|
+
validator ??= new HtmlValidate(new ResolvedOnceLoader({
|
|
58
|
+
extends: ['html-validate:standard', 'html-validate:a11y', 'html-validate:document'],
|
|
59
|
+
rules: {
|
|
60
|
+
// Subresource integrity only protects resources from other origins.
|
|
61
|
+
'require-sri': ['error', { target: 'crossorigin' }],
|
|
62
|
+
// Covered by the document-title rule, which can also fix it.
|
|
63
|
+
'empty-title': 'off',
|
|
64
|
+
// Not accessibility problems: worth knowing, not worth failing a build.
|
|
65
|
+
'long-title': 'warn',
|
|
66
|
+
'no-implicit-button-type': 'warn',
|
|
67
|
+
'no-redundant-role': 'warn',
|
|
68
|
+
'prefer-button': 'warn',
|
|
69
|
+
'prefer-native-element': 'warn',
|
|
70
|
+
'prefer-tbody': 'warn',
|
|
71
|
+
'tel-non-breaking': 'warn',
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
return validator;
|
|
75
|
+
}
|
|
76
|
+
/** Validate one built page with html-validate. */
|
|
77
|
+
export async function validateHtml(page, html) {
|
|
78
|
+
const report = await getValidator().validateString(html, page.output);
|
|
79
|
+
const findings = [];
|
|
80
|
+
for (const result of report.results) {
|
|
81
|
+
for (const message of result.messages) {
|
|
82
|
+
if (COVERED.some(([ruleId, text]) => message.ruleId === ruleId && message.message.includes(text))) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const wcag = WCAG[message.ruleId];
|
|
86
|
+
findings.push({
|
|
87
|
+
layer: 'output',
|
|
88
|
+
diagnostic: {
|
|
89
|
+
ruleId: ALIASES[message.ruleId] ?? `html/${message.ruleId}`,
|
|
90
|
+
severity: message.severity >= 2 ? 'error' : 'warning',
|
|
91
|
+
message: message.message,
|
|
92
|
+
...(page.source !== undefined && { file: page.source }),
|
|
93
|
+
url: page.url,
|
|
94
|
+
...(message.selector !== null && { selector: message.selector }),
|
|
95
|
+
...(wcag !== undefined && { wcag }),
|
|
96
|
+
...(message.ruleUrl !== undefined && { helpUrl: message.ruleUrl }),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return findings;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=html-validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-validate.js","sourceRoot":"","sources":["../src/html-validate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './check.js';
|
|
2
|
+
export { INSTALL_BROWSER_COMMAND, PLAYWRIGHT_VERSION, type WcagLevel } from './headless.js';
|
|
3
|
+
export * from './report/index.js';
|
|
4
|
+
export type { CheckPage, Finding, Layer } from './types.js';
|
|
5
|
+
export { CheckCache } from './cache.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAC5F,cAAc,mBAAmB,CAAC;AAClC,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAkB,MAAM,eAAe,CAAC;AAC5F,cAAc,mBAAmB,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Element, Root } from 'hast';
|
|
2
|
+
import type { CheckPage, Finding } from './types.js';
|
|
3
|
+
export interface OutputContext {
|
|
4
|
+
page: CheckPage;
|
|
5
|
+
/** The built page, parsed. */
|
|
6
|
+
tree: Root;
|
|
7
|
+
/** Whether a POSIX path relative to the build directory is a file. */
|
|
8
|
+
exists: (path: string) => boolean;
|
|
9
|
+
/** Element ids of a built HTML file, by output path. */
|
|
10
|
+
idsOf: (output: string) => ReadonlySet<string> | undefined;
|
|
11
|
+
trailingSlash: 'always' | 'never';
|
|
12
|
+
}
|
|
13
|
+
interface Visit {
|
|
14
|
+
node: Element;
|
|
15
|
+
ancestors: Element[];
|
|
16
|
+
}
|
|
17
|
+
/** Every element with its ancestors, in document order. */
|
|
18
|
+
export declare function elementsOf(tree: Root): Visit[];
|
|
19
|
+
/** Element ids (and legacy `<a name>` anchors) of a parsed page. */
|
|
20
|
+
export declare function idsOf(tree: Root): Set<string>;
|
|
21
|
+
/** A short CSS path like `#main > article > p:nth-child(3) > img`. */
|
|
22
|
+
export declare function selectorOf({ node, ancestors }: Visit): string;
|
|
23
|
+
export declare function checkOutput(ctx: OutputContext): Finding[];
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=output-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-rules.d.ts","sourceRoot":"","sources":["../src/output-rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAe,MAAM,MAAM,CAAC;AAIvD,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,8BAA8B;IAC9B,IAAI,EAAE,IAAI,CAAC;IACX,sEAAsE;IACtE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,wDAAwD;IACxD,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAC3D,aAAa,EAAE,QAAQ,GAAG,OAAO,CAAC;CACnC;AAED,UAAU,KAAK;IACb,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,KAAK,EAAE,CAa9C;AAED,oEAAoE;AACpE,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAS7C;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,GAAG,MAAM,CAkB7D;AA0BD,wBAAgB,WAAW,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,EAAE,CA8JzD"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { toString as hastToString } from 'hast-util-to-string';
|
|
2
|
+
import { resolveRequest } from '@staticforge/server';
|
|
3
|
+
/** Every element with its ancestors, in document order. */
|
|
4
|
+
export function elementsOf(tree) {
|
|
5
|
+
const out = [];
|
|
6
|
+
const walk = (children, ancestors) => {
|
|
7
|
+
for (const child of children) {
|
|
8
|
+
if (child.type !== 'element')
|
|
9
|
+
continue;
|
|
10
|
+
out.push({ node: child, ancestors });
|
|
11
|
+
walk(child.children, [...ancestors, child]);
|
|
12
|
+
if (child.tagName === 'template' && child.content)
|
|
13
|
+
walk(child.content.children, [...ancestors, child]);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
walk(tree.children, []);
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
/** Element ids (and legacy `<a name>` anchors) of a parsed page. */
|
|
20
|
+
export function idsOf(tree) {
|
|
21
|
+
const ids = new Set();
|
|
22
|
+
for (const { node } of elementsOf(tree)) {
|
|
23
|
+
const id = node.properties.id;
|
|
24
|
+
if (typeof id === 'string' && id !== '')
|
|
25
|
+
ids.add(id);
|
|
26
|
+
const name = node.properties.name;
|
|
27
|
+
if (node.tagName === 'a' && typeof name === 'string' && name !== '')
|
|
28
|
+
ids.add(name);
|
|
29
|
+
}
|
|
30
|
+
return ids;
|
|
31
|
+
}
|
|
32
|
+
/** A short CSS path like `#main > article > p:nth-child(3) > img`. */
|
|
33
|
+
export function selectorOf({ node, ancestors }) {
|
|
34
|
+
const parts = [];
|
|
35
|
+
const chain = [...ancestors, node];
|
|
36
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
37
|
+
const el = chain[i];
|
|
38
|
+
const id = el.properties.id;
|
|
39
|
+
if (typeof id === 'string' && /^[A-Za-z][\w-]*$/.test(id)) {
|
|
40
|
+
parts.unshift(`#${id}`);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
const parent = chain[i - 1];
|
|
44
|
+
const siblings = parent?.children.filter((c) => c.type === 'element') ?? [];
|
|
45
|
+
const sameTag = siblings.filter((s) => s.tagName === el.tagName);
|
|
46
|
+
parts.unshift(sameTag.length > 1 ? `${el.tagName}:nth-child(${siblings.indexOf(el) + 1})` : el.tagName);
|
|
47
|
+
}
|
|
48
|
+
return parts.join(' > ');
|
|
49
|
+
}
|
|
50
|
+
const text = (node) => hastToString(node).replace(/\s+/g, ' ').trim();
|
|
51
|
+
const attr = (node, name) => {
|
|
52
|
+
const value = node.properties[name];
|
|
53
|
+
if (typeof value === 'string')
|
|
54
|
+
return value;
|
|
55
|
+
if (Array.isArray(value))
|
|
56
|
+
return value.join(' ');
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
const URL_ATTRIBUTES = {
|
|
60
|
+
a: ['href'],
|
|
61
|
+
area: ['href'],
|
|
62
|
+
link: ['href'],
|
|
63
|
+
script: ['src'],
|
|
64
|
+
img: ['src', 'srcSet'],
|
|
65
|
+
source: ['src', 'srcSet'],
|
|
66
|
+
video: ['src', 'poster'],
|
|
67
|
+
audio: ['src'],
|
|
68
|
+
track: ['src'],
|
|
69
|
+
iframe: ['src'],
|
|
70
|
+
embed: ['src'],
|
|
71
|
+
};
|
|
72
|
+
const SCHEME = /^[a-z][a-z\d+.-]*:/i;
|
|
73
|
+
export function checkOutput(ctx) {
|
|
74
|
+
const { page, tree } = ctx;
|
|
75
|
+
const findings = [];
|
|
76
|
+
const all = elementsOf(tree);
|
|
77
|
+
const report = (diagnostic, at, match) => {
|
|
78
|
+
findings.push({
|
|
79
|
+
layer: 'output',
|
|
80
|
+
diagnostic: {
|
|
81
|
+
...diagnostic,
|
|
82
|
+
...(page.source !== undefined && { file: page.source }),
|
|
83
|
+
url: page.url,
|
|
84
|
+
...(at && { selector: selectorOf(at) }),
|
|
85
|
+
},
|
|
86
|
+
...(match !== undefined && { match }),
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const find = (tagName) => all.find((v) => v.node.tagName === tagName);
|
|
90
|
+
// html-lang
|
|
91
|
+
const html = find('html');
|
|
92
|
+
if (html !== undefined && (attr(html.node, 'lang') ?? '').trim() === '') {
|
|
93
|
+
report({
|
|
94
|
+
ruleId: 'html-lang',
|
|
95
|
+
severity: 'error',
|
|
96
|
+
wcag: '3.1.1',
|
|
97
|
+
message: 'The <html> element has no lang attribute',
|
|
98
|
+
hint: 'Set lang on <html> in the layout (or the document), e.g. lang="en".',
|
|
99
|
+
}, html);
|
|
100
|
+
}
|
|
101
|
+
// document-title
|
|
102
|
+
const title = find('title');
|
|
103
|
+
if (title === undefined || text(title.node) === '') {
|
|
104
|
+
report({
|
|
105
|
+
ruleId: 'document-title',
|
|
106
|
+
severity: 'error',
|
|
107
|
+
wcag: '2.4.2',
|
|
108
|
+
message: title === undefined ? 'Page has no <title>' : 'Page has an empty <title>',
|
|
109
|
+
}, title ?? html);
|
|
110
|
+
}
|
|
111
|
+
// meta-viewport
|
|
112
|
+
const viewport = all.find((v) => v.node.tagName === 'meta' && attr(v.node, 'name') === 'viewport');
|
|
113
|
+
if (viewport === undefined) {
|
|
114
|
+
report({
|
|
115
|
+
ruleId: 'meta-viewport',
|
|
116
|
+
severity: 'warning',
|
|
117
|
+
wcag: '1.4.10',
|
|
118
|
+
message: 'Page has no <meta name="viewport">, so it will not reflow on small screens',
|
|
119
|
+
hint: 'Add <meta name="viewport" content="width=device-width, initial-scale=1">.',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const content = (attr(viewport.node, 'content') ?? '').toLowerCase();
|
|
124
|
+
const scalable = /user-scalable\s*=\s*(no|0)\b/.exec(content);
|
|
125
|
+
const maxScale = /maximum-scale\s*=\s*([\d.]+)/.exec(content);
|
|
126
|
+
if (scalable !== null || (maxScale?.[1] !== undefined && Number(maxScale[1]) < 2)) {
|
|
127
|
+
report({
|
|
128
|
+
ruleId: 'meta-viewport',
|
|
129
|
+
severity: 'error',
|
|
130
|
+
wcag: '1.4.4',
|
|
131
|
+
message: 'The viewport meta tag prevents zooming',
|
|
132
|
+
hint: 'Remove user-scalable=no and any maximum-scale below 2.',
|
|
133
|
+
}, viewport);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// landmark-main
|
|
137
|
+
const main = all.find((v) => v.node.tagName === 'main' || attr(v.node, 'role') === 'main');
|
|
138
|
+
if (main === undefined) {
|
|
139
|
+
report({
|
|
140
|
+
ruleId: 'landmark-main',
|
|
141
|
+
severity: 'warning',
|
|
142
|
+
wcag: '1.3.1',
|
|
143
|
+
message: 'Page has no <main> landmark',
|
|
144
|
+
hint: 'Wrap the primary content in <main id="main">.',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// skip-link: pages with repeated blocks (header/nav) need a way past them.
|
|
148
|
+
const body = find('body');
|
|
149
|
+
const hasBlocks = all.some((v) => v.node.tagName === 'nav' || v.node.tagName === 'header');
|
|
150
|
+
if (body !== undefined && hasBlocks) {
|
|
151
|
+
const firstLink = all.find((v) => v.node.tagName === 'a' && attr(v.node, 'href') !== undefined);
|
|
152
|
+
const href = firstLink === undefined ? undefined : attr(firstLink.node, 'href');
|
|
153
|
+
const target = href?.startsWith('#') ? safeDecode(href.slice(1)) : undefined;
|
|
154
|
+
const ids = idsOf(tree);
|
|
155
|
+
if (target === undefined || target === '' || !ids.has(target)) {
|
|
156
|
+
report({
|
|
157
|
+
ruleId: 'skip-link',
|
|
158
|
+
severity: 'warning',
|
|
159
|
+
wcag: '2.4.1',
|
|
160
|
+
message: 'The first link on the page is not a skip link to the main content',
|
|
161
|
+
hint: 'Start <body> with <a class="skip-link" href="#main">Skip to content</a>.',
|
|
162
|
+
}, firstLink);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const visit of all) {
|
|
166
|
+
const { node } = visit;
|
|
167
|
+
// video-captions
|
|
168
|
+
if (node.tagName === 'video') {
|
|
169
|
+
const hasCaptions = node.children.some((c) => c.type === 'element' &&
|
|
170
|
+
c.tagName === 'track' &&
|
|
171
|
+
['captions', 'subtitles'].includes(attr(c, 'kind') ?? ''));
|
|
172
|
+
if (!hasCaptions) {
|
|
173
|
+
report({
|
|
174
|
+
ruleId: 'video-captions',
|
|
175
|
+
severity: 'warning',
|
|
176
|
+
wcag: '1.2.2',
|
|
177
|
+
message: 'Video has no captions track',
|
|
178
|
+
hint: 'Add <track kind="captions" src="…vtt" srclang="en">, unless the video has no speech.',
|
|
179
|
+
}, visit);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// iframe-title
|
|
183
|
+
if (node.tagName === 'iframe' &&
|
|
184
|
+
(attr(node, 'title') ?? '').trim() === '' &&
|
|
185
|
+
String(node.properties.ariaHidden) !== 'true') {
|
|
186
|
+
report({
|
|
187
|
+
ruleId: 'iframe-title',
|
|
188
|
+
severity: 'error',
|
|
189
|
+
wcag: '4.1.2',
|
|
190
|
+
message: '<iframe> has no title describing its content',
|
|
191
|
+
}, visit);
|
|
192
|
+
}
|
|
193
|
+
checkUrls(visit, ctx, report);
|
|
194
|
+
}
|
|
195
|
+
return findings;
|
|
196
|
+
}
|
|
197
|
+
const safeDecode = (value) => {
|
|
198
|
+
try {
|
|
199
|
+
return decodeURIComponent(value);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
function checkUrls(visit, ctx, report) {
|
|
206
|
+
const { node } = visit;
|
|
207
|
+
for (const name of URL_ATTRIBUTES[node.tagName] ?? []) {
|
|
208
|
+
const raw = node.properties[name];
|
|
209
|
+
if (typeof raw !== 'string' && !Array.isArray(raw))
|
|
210
|
+
continue;
|
|
211
|
+
const values = name === 'srcSet'
|
|
212
|
+
? String(Array.isArray(raw) ? raw.join(',') : raw)
|
|
213
|
+
.split(',')
|
|
214
|
+
.map((candidate) => candidate.trim().split(/\s+/)[0] ?? '')
|
|
215
|
+
: [String(Array.isArray(raw) ? raw.join(' ') : raw)];
|
|
216
|
+
for (const value of values) {
|
|
217
|
+
if (value === '' || SCHEME.test(value) || value.startsWith('//'))
|
|
218
|
+
continue;
|
|
219
|
+
const url = new URL(value, `http://site.invalid${ctx.page.url}`);
|
|
220
|
+
const isLink = node.tagName === 'a' || node.tagName === 'area';
|
|
221
|
+
// Unresolved page sources (e.g. other.md) are reported by the build.
|
|
222
|
+
if (/\.(?:md|markdown)$/i.test(url.pathname))
|
|
223
|
+
continue;
|
|
224
|
+
const resolution = resolveRequest(url.pathname, ctx.exists, {
|
|
225
|
+
trailingSlash: ctx.trailingSlash,
|
|
226
|
+
});
|
|
227
|
+
if (resolution.kind === 'redirect') {
|
|
228
|
+
report({
|
|
229
|
+
ruleId: 'link-trailing-slash',
|
|
230
|
+
severity: 'warning',
|
|
231
|
+
message: `Link to ${value} redirects to ${resolution.location}`,
|
|
232
|
+
hint: "Link to the page's canonical URL to save a redirect.",
|
|
233
|
+
}, visit, url.pathname);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (resolution.kind === 'not-found' || resolution.status === 404) {
|
|
237
|
+
report({
|
|
238
|
+
ruleId: 'link-broken',
|
|
239
|
+
severity: 'error',
|
|
240
|
+
message: isLink
|
|
241
|
+
? `Link to ${value} does not match any page or file in the built site`
|
|
242
|
+
: `<${node.tagName}> ${name.toLowerCase()} ${value} does not exist in the built site`,
|
|
243
|
+
}, visit, url.pathname);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const fragment = safeDecode(url.hash.slice(1));
|
|
247
|
+
if (!isLink ||
|
|
248
|
+
fragment === '' ||
|
|
249
|
+
fragment.toLowerCase() === 'top' ||
|
|
250
|
+
fragment.startsWith(':~:')) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const ids = ctx.idsOf(resolution.path);
|
|
254
|
+
if (ids !== undefined && !ids.has(fragment)) {
|
|
255
|
+
report({
|
|
256
|
+
ruleId: 'link-fragment',
|
|
257
|
+
severity: 'warning',
|
|
258
|
+
message: `Link to ${value} points to #${fragment}, which is not an id on that page`,
|
|
259
|
+
hint: 'Check the heading or element id; heading ids are derived from their text.',
|
|
260
|
+
}, visit);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=output-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-rules.js","sourceRoot":"","sources":["../src/output-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/report/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CheckResult } from '../check.js';
|
|
2
|
+
/**
|
|
3
|
+
* JUnit XML: one test suite per file (or page), one test case per problem.
|
|
4
|
+
* Problems at or above `failOn` are failures; the rest pass, with the problem
|
|
5
|
+
* in system-out.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatJunit(result: CheckResult): string;
|
|
8
|
+
//# sourceMappingURL=junit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"junit.d.ts","sourceRoot":"","sources":["../../src/report/junit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW/C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CA8CvD"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const xml = (text) => text
|
|
2
|
+
.replace(/&/g, '&')
|
|
3
|
+
.replace(/</g, '<')
|
|
4
|
+
.replace(/>/g, '>')
|
|
5
|
+
.replace(/"/g, '"')
|
|
6
|
+
// eslint-disable-next-line no-control-regex
|
|
7
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '');
|
|
8
|
+
/**
|
|
9
|
+
* JUnit XML: one test suite per file (or page), one test case per problem.
|
|
10
|
+
* Problems at or above `failOn` are failures; the rest pass, with the problem
|
|
11
|
+
* in system-out.
|
|
12
|
+
*/
|
|
13
|
+
export function formatJunit(result) {
|
|
14
|
+
const failing = (d) => d.severity === 'error' || (result.failOn === 'warning' && d.severity === 'warning');
|
|
15
|
+
const suites = new Map();
|
|
16
|
+
for (const d of result.diagnostics) {
|
|
17
|
+
const key = d.file ?? d.url ?? 'site';
|
|
18
|
+
suites.set(key, [...(suites.get(key) ?? []), d]);
|
|
19
|
+
}
|
|
20
|
+
const failures = result.diagnostics.filter(failing).length;
|
|
21
|
+
const out = [
|
|
22
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
23
|
+
`<testsuites name="sforge check" tests="${result.diagnostics.length}" failures="${failures}" time="${(result.durationMs / 1000).toFixed(3)}">`,
|
|
24
|
+
];
|
|
25
|
+
for (const [name, diagnostics] of suites) {
|
|
26
|
+
out.push(` <testsuite name="${xml(name)}" tests="${diagnostics.length}" failures="${diagnostics.filter(failing).length}">`);
|
|
27
|
+
for (const d of diagnostics) {
|
|
28
|
+
const location = d.line !== undefined ? `${name}:${d.line}:${d.column ?? 1}` : name;
|
|
29
|
+
const details = [
|
|
30
|
+
`${location} ${d.severity} ${d.ruleId}`,
|
|
31
|
+
d.message,
|
|
32
|
+
d.url !== undefined ? `Page: ${d.url}` : undefined,
|
|
33
|
+
d.selector !== undefined ? `Element: ${d.selector}` : undefined,
|
|
34
|
+
d.wcag !== undefined ? `WCAG: ${d.wcag}` : undefined,
|
|
35
|
+
d.hint,
|
|
36
|
+
d.helpUrl,
|
|
37
|
+
]
|
|
38
|
+
.filter((s) => s !== undefined)
|
|
39
|
+
.join('\n');
|
|
40
|
+
out.push(` <testcase classname="${xml(name)}" name="${xml(`${d.ruleId}: ${d.message}`)}">`);
|
|
41
|
+
out.push(failing(d)
|
|
42
|
+
? ` <failure type="${xml(d.ruleId)}" message="${xml(d.message)}">${xml(details)}</failure>`
|
|
43
|
+
: ` <system-out>${xml(details)}</system-out>`);
|
|
44
|
+
out.push(' </testcase>');
|
|
45
|
+
}
|
|
46
|
+
out.push(' </testsuite>');
|
|
47
|
+
}
|
|
48
|
+
out.push('</testsuites>', '');
|
|
49
|
+
return out.join('\n');
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=junit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"junit.js","sourceRoot":"","sources":["../../src/report/junit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CheckResult } from '../check.js';
|
|
2
|
+
/**
|
|
3
|
+
* Human-readable report, grouped by file. A problem that appears at the same
|
|
4
|
+
* place on several pages (usually the theme) is listed once, with its pages.
|
|
5
|
+
*/
|
|
6
|
+
export declare function formatPretty(result: CheckResult, options?: {
|
|
7
|
+
color?: boolean;
|
|
8
|
+
}): string;
|
|
9
|
+
//# sourceMappingURL=pretty.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pretty.d.ts","sourceRoot":"","sources":["../../src/report/pretty.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAY/C;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAmF3F"}
|