c3b-lint 0.0.12 → 0.0.14
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/README.md +2 -2
- package/check/html.js +26 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,8 +19,8 @@ markdown that a tool can check, with one command and one config to install. It r
|
|
|
19
19
|
`og:image`, `twitter:card`, one SVG favicon; a `noindex` page is excused the canonical and social tags), no external
|
|
20
20
|
stylesheets (webfonts are self-hosted), stylesheet order (`reset`, `tokens`, `main`, …, page file last),
|
|
21
21
|
the skip-link rule (`<a id="skip-link" href="#main">` first in `<body>` when there is a header or nav, absent
|
|
22
|
-
otherwise, `<main id="main">`), one `<h1>`, `alt` on every image, no inline handlers, no `<style>` or `style=""`
|
|
23
|
-
(except a JS-driven custom property), relative paths only, kebab-case ids and classes, no duplicate ids,
|
|
22
|
+
otherwise, landing on `<main id="main">` — or on the id `<main>` already has), one `<h1>`, `alt` on every image, no inline handlers, no `<style>` or `style=""`
|
|
23
|
+
(except a JS-driven custom property), relative paths only (a Barba site's links outside its container — header, menu — stay root-relative), kebab-case ids and classes, no duplicate ids,
|
|
24
24
|
`integrity` on CDN scripts.
|
|
25
25
|
5. **Project structure**: singular directory names, lowercase kebab-case asset names (and no camera exports), and,
|
|
26
26
|
when `src/` holds pages, CSS only in `src/style/` + `style/page/` + `style/animation/`, JS only in `src/script/`
|
package/check/html.js
CHANGED
|
@@ -26,6 +26,19 @@ const toPosix = (p) => p.split(sep).join('/');
|
|
|
26
26
|
const attr = (el, name) => el.attrs?.find((a) => a.name === name)?.value;
|
|
27
27
|
const line = (el) => el.sourceCodeLocation?.startLine ?? 0;
|
|
28
28
|
const isElement = (node) => Boolean(node.tagName);
|
|
29
|
+
const isBarbaContainer = (node) => isElement(node) && attr(node, 'data-barba') === 'container';
|
|
30
|
+
|
|
31
|
+
// Inside Barba's page container: replaced on every navigation, so its links resolve against the URL
|
|
32
|
+
// of the page they came with
|
|
33
|
+
function inBarbaContainer(el) {
|
|
34
|
+
for (let node = el.parentNode; node; node = node.parentNode) {
|
|
35
|
+
if (isBarbaContainer(node)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
29
42
|
|
|
30
43
|
// Every element in document order
|
|
31
44
|
function* elements(node) {
|
|
@@ -138,11 +151,14 @@ function checkPage(report, file, source) {
|
|
|
138
151
|
const skipLink = first && first.tagName === 'a' && attr(first, 'id') === 'skip-link';
|
|
139
152
|
const main = all.find((el) => el.name === 'main');
|
|
140
153
|
if (hasChrome) {
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
// The skip link lands on <main> by its id: "main" by default, or the id <main> already has
|
|
155
|
+
// (a Barba site scopes each page's CSS by it)
|
|
156
|
+
const target = main && attr(main, 'id');
|
|
157
|
+
if (!target) {
|
|
158
|
+
report.error(file, '<main> carries an id for the skip link to land on (id="main", or the one it already has)');
|
|
143
159
|
}
|
|
144
|
-
if (!
|
|
145
|
-
report.error(file,
|
|
160
|
+
if (!skipLink || attr(first, 'href') !== `#${ target || 'main' }`) {
|
|
161
|
+
report.error(file, `a page with a header or nav starts <body> with <a id="skip-link" href="#${ target || 'main' }">`);
|
|
146
162
|
}
|
|
147
163
|
} else if (skipLink) {
|
|
148
164
|
report.error(at(first), 'a page with no header or nav has no skip link');
|
|
@@ -154,6 +170,10 @@ function checkPage(report, file, source) {
|
|
|
154
170
|
report.error(h1s[1] ? at(h1s[1]) : file, `one <h1> per page (found ${ h1s.length })`);
|
|
155
171
|
}
|
|
156
172
|
const ids = new Map();
|
|
173
|
+
// A Barba site keeps everything outside its container (header, menu) for the whole visit while the URL
|
|
174
|
+
// changes under it, so a relative link there would resolve against whichever page is showing: those
|
|
175
|
+
// links stay root-relative
|
|
176
|
+
const barba = all.some((el) => attr(el, 'data-barba') === 'wrapper');
|
|
157
177
|
for (const el of all) {
|
|
158
178
|
for (const { name, value } of el.attrs ?? []) {
|
|
159
179
|
if (/^on[a-z]+$/.test(name)) {
|
|
@@ -162,7 +182,8 @@ function checkPage(report, file, source) {
|
|
|
162
182
|
if (name === 'style' && !/^\s*(--[a-z0-9-]+\s*:[^;]+;?\s*)+$/.test(value)) {
|
|
163
183
|
report.error(at(el), 'no style="" attributes, except to set a JS-driven custom property');
|
|
164
184
|
}
|
|
165
|
-
|
|
185
|
+
const persistentLink = barba && el.name === 'a' && name === 'href' && !inBarbaContainer(el);
|
|
186
|
+
if ((name === 'href' || name === 'src') && /^\/(?!\/)/.test(value) && !persistentLink) {
|
|
166
187
|
report.error(at(el), `root-relative path ${ value } — use a relative path`);
|
|
167
188
|
}
|
|
168
189
|
if (name === 'id') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c3b-lint",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Lints a co:lab project against the rules a tool can check: ESLint, Stylelint, the 500-line limit and the shared .editorconfig; --init writes the config files.",
|
|
5
5
|
"license": "GPL-3.0",
|
|
6
6
|
"type": "module",
|