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 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
- if (!skipLink || attr(first, 'href') !== '#main') {
142
- report.error(file, 'a page with a header or nav starts <body> with <a id="skip-link" href="#main">');
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 (!main || attr(main, 'id') !== 'main') {
145
- report.error(file, '<main> carries id="main" for the skip link');
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
- if ((name === 'href' || name === 'src') && /^\/(?!\/)/.test(value)) {
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.12",
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",