@rivi-software/agentic-seo-check 0.1.0 → 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.
Files changed (119) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +65 -15
  3. package/dist/brand.d.ts +11 -0
  4. package/dist/brand.js +22 -0
  5. package/dist/brand.js.map +1 -0
  6. package/dist/cli.js +125 -80
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/audit.d.ts +16 -16
  9. package/dist/core/audit.js +363 -248
  10. package/dist/core/audit.js.map +1 -1
  11. package/dist/core/concurrency.d.ts +4 -4
  12. package/dist/core/concurrency.js +33 -23
  13. package/dist/core/concurrency.js.map +1 -1
  14. package/dist/core/crawl.d.ts +5 -5
  15. package/dist/core/crawl.js +250 -121
  16. package/dist/core/crawl.js.map +1 -1
  17. package/dist/core/fetch.d.ts +5 -5
  18. package/dist/core/fetch.js +151 -100
  19. package/dist/core/fetch.js.map +1 -1
  20. package/dist/core/html/dom.d.ts +13 -0
  21. package/dist/core/html/dom.js +17 -0
  22. package/dist/core/html/dom.js.map +1 -0
  23. package/dist/core/html/extract.d.ts +2 -2
  24. package/dist/core/html/extract.js +168 -111
  25. package/dist/core/html/extract.js.map +1 -1
  26. package/dist/core/html/structured-data.d.ts +16 -6
  27. package/dist/core/html/structured-data.js +180 -145
  28. package/dist/core/html/structured-data.js.map +1 -1
  29. package/dist/core/html/text.d.ts +14 -14
  30. package/dist/core/html/text.js +110 -107
  31. package/dist/core/html/text.js.map +1 -1
  32. package/dist/core/lighthouse.d.ts +26 -25
  33. package/dist/core/lighthouse.js +181 -105
  34. package/dist/core/lighthouse.js.map +1 -1
  35. package/dist/core/performance-rules.d.ts +2 -2
  36. package/dist/core/performance-rules.js +105 -33
  37. package/dist/core/performance-rules.js.map +1 -1
  38. package/dist/core/render-rules.d.ts +5 -4
  39. package/dist/core/render-rules.js +129 -62
  40. package/dist/core/render-rules.js.map +1 -1
  41. package/dist/core/render.d.ts +2 -2
  42. package/dist/core/render.js +29 -23
  43. package/dist/core/render.js.map +1 -1
  44. package/dist/core/report/markdown.d.ts +2 -2
  45. package/dist/core/report/markdown.js +110 -131
  46. package/dist/core/report/markdown.js.map +1 -1
  47. package/dist/core/robots.d.ts +5 -5
  48. package/dist/core/robots.js +31 -16
  49. package/dist/core/robots.js.map +1 -1
  50. package/dist/core/rules/canonical.d.ts +1 -1
  51. package/dist/core/rules/canonical.js +93 -57
  52. package/dist/core/rules/canonical.js.map +1 -1
  53. package/dist/core/rules/content.js +40 -26
  54. package/dist/core/rules/content.js.map +1 -1
  55. package/dist/core/rules/document.js +64 -41
  56. package/dist/core/rules/document.js.map +1 -1
  57. package/dist/core/rules/headings.js +58 -38
  58. package/dist/core/rules/headings.js.map +1 -1
  59. package/dist/core/rules/hreflang.js +70 -38
  60. package/dist/core/rules/hreflang.js.map +1 -1
  61. package/dist/core/rules/images.js +44 -26
  62. package/dist/core/rules/images.js.map +1 -1
  63. package/dist/core/rules/index.d.ts +6 -6
  64. package/dist/core/rules/index.js +36 -35
  65. package/dist/core/rules/index.js.map +1 -1
  66. package/dist/core/rules/indexing.d.ts +2 -2
  67. package/dist/core/rules/indexing.js +48 -26
  68. package/dist/core/rules/indexing.js.map +1 -1
  69. package/dist/core/rules/keyword.js +84 -47
  70. package/dist/core/rules/keyword.js.map +1 -1
  71. package/dist/core/rules/links.js +72 -37
  72. package/dist/core/rules/links.js.map +1 -1
  73. package/dist/core/rules/meta.js +55 -34
  74. package/dist/core/rules/meta.js.map +1 -1
  75. package/dist/core/rules/response.js +75 -41
  76. package/dist/core/rules/response.js.map +1 -1
  77. package/dist/core/rules/rule.d.ts +6 -6
  78. package/dist/core/rules/rule.js +36 -36
  79. package/dist/core/rules/rule.js.map +1 -1
  80. package/dist/core/rules/social.js +86 -45
  81. package/dist/core/rules/social.js.map +1 -1
  82. package/dist/core/rules/structured-data.d.ts +6 -4
  83. package/dist/core/rules/structured-data.js +314 -102
  84. package/dist/core/rules/structured-data.js.map +1 -1
  85. package/dist/core/rules/title.js +48 -33
  86. package/dist/core/rules/title.js.map +1 -1
  87. package/dist/core/score.d.ts +3 -3
  88. package/dist/core/score.js +11 -9
  89. package/dist/core/score.js.map +1 -1
  90. package/dist/core/site-rules.d.ts +3 -3
  91. package/dist/core/site-rules.js +409 -198
  92. package/dist/core/site-rules.js.map +1 -1
  93. package/dist/core/sitemap.d.ts +5 -5
  94. package/dist/core/sitemap.js +109 -64
  95. package/dist/core/sitemap.js.map +1 -1
  96. package/dist/core/static.d.ts +2 -2
  97. package/dist/core/static.js +209 -112
  98. package/dist/core/static.js.map +1 -1
  99. package/dist/core/summary.d.ts +2 -2
  100. package/dist/core/summary.js +11 -6
  101. package/dist/core/summary.js.map +1 -1
  102. package/dist/core/types.d.ts +14 -14
  103. package/dist/core/types.js +15 -8
  104. package/dist/core/types.js.map +1 -1
  105. package/dist/core/url.d.ts +11 -11
  106. package/dist/core/url.js +122 -70
  107. package/dist/core/url.js.map +1 -1
  108. package/dist/index.d.ts +11 -11
  109. package/dist/index.js +9 -9
  110. package/dist/index.js.map +1 -1
  111. package/dist/mcp-bin.js +7 -4
  112. package/dist/mcp-bin.js.map +1 -1
  113. package/dist/mcp.d.ts +3 -3
  114. package/dist/mcp.js +312 -122
  115. package/dist/mcp.js.map +1 -1
  116. package/dist/version.d.ts +0 -1
  117. package/dist/version.js +4 -2
  118. package/dist/version.js.map +1 -1
  119. package/package.json +27 -9
@@ -1,105 +1,141 @@
1
- import { hostOf, normalizeUrl, samePath } from '../url.js';
2
- import { cap, defineRule, GOOGLE_DOCS } from './rule.js';
1
+ import { hostOf, normalizeUrl, samePath } from "../url.js";
2
+ import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
3
3
  const SELECTOR = 'head > link[rel="canonical"]';
4
- const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '0.0.0.0', '::1', '[::1]']);
5
- export function isLocalHost(url) {
4
+ const LOCAL_HOSTS = new Set([
5
+ "localhost",
6
+ "127.0.0.1",
7
+ "0.0.0.0",
8
+ "::1",
9
+ "[::1]",
10
+ ]);
11
+ export const isLocalHost = (url) => {
6
12
  const host = hostOf(url);
7
- if (!host)
13
+ if (!host) {
8
14
  return false;
9
- return LOCAL_HOSTS.has(host) || host.endsWith('.local') || host.endsWith('.localhost') || host.endsWith('.test');
10
- }
15
+ }
16
+ return (LOCAL_HOSTS.has(host) ||
17
+ host.endsWith(".local") ||
18
+ host.endsWith(".localhost") ||
19
+ host.endsWith(".test"));
20
+ };
21
+ const stripTrailingSlash = (url) => url.endsWith("/") && new URL(url).pathname !== "/" ? url.slice(0, -1) : url;
11
22
  export const canonicalRules = [
12
23
  defineRule({
13
- id: 'canonical.missing',
14
- category: 'canonical',
15
- severity: 'warning',
24
+ id: "canonical.missing",
25
+ category: "canonical",
26
+ severity: "warning",
16
27
  description: 'No <link rel="canonical"> is present.',
17
- recommendation: 'Add a self-referencing canonical with the absolute preferred URL (scheme, host, trailing slash) so duplicate URLs (query strings, www/non-www) consolidate to it.',
28
+ recommendation: "Add a self-referencing canonical with the absolute preferred URL (scheme, host, trailing slash) so duplicate URLs (query strings, www/non-www) consolidate to it.",
18
29
  docs: GOOGLE_DOCS.canonical,
19
- }, ({ page }, make) => (page.canonicals.length === 0 ? make('Page has no canonical link.', { selector: SELECTOR }) : null)),
30
+ }, ({ page }, make) => page.canonicals.length === 0
31
+ ? make("Page has no canonical link.", { selector: SELECTOR })
32
+ : null),
20
33
  defineRule({
21
- id: 'canonical.multiple',
22
- category: 'canonical',
23
- severity: 'error',
24
- description: 'More than one canonical link is present. When they disagree, search engines ignore all of them.',
25
- recommendation: 'Emit exactly one canonical link per page. Check for a framework default plus a hand-written one.',
34
+ id: "canonical.multiple",
35
+ category: "canonical",
36
+ severity: "error",
37
+ description: "More than one canonical link is present. When they disagree, search engines ignore all of them.",
38
+ recommendation: "Emit exactly one canonical link per page. Check for a framework default plus a hand-written one.",
26
39
  docs: GOOGLE_DOCS.canonical,
27
40
  }, ({ page }, make) => {
28
- if (page.canonicals.length <= 1)
41
+ if (page.canonicals.length <= 1) {
29
42
  return null;
43
+ }
30
44
  const distinct = new Set(page.canonicals.map((c) => c.trim()));
31
- return make(`Page has ${page.canonicals.length} canonical links${distinct.size > 1 ? ' with different targets' : ' (identical)'}.`, {
45
+ return make(`Page has ${page.canonicals.length} canonical links${distinct.size > 1 ? " with different targets" : " (identical)"}.`, {
32
46
  selector: SELECTOR,
33
- severity: distinct.size > 1 ? 'error' : 'warning',
47
+ severity: distinct.size > 1 ? "error" : "warning",
34
48
  evidence: { canonicals: cap(page.canonicals) },
35
49
  });
36
50
  }),
37
51
  defineRule({
38
- id: 'canonical.invalid',
39
- category: 'canonical',
40
- severity: 'error',
41
- description: 'The canonical href is empty or cannot be resolved to an http(s) URL.',
42
- recommendation: 'Set the canonical href to a valid absolute URL such as https://www.example.com/path/.',
52
+ id: "canonical.invalid",
53
+ category: "canonical",
54
+ severity: "error",
55
+ description: "The canonical href is empty or cannot be resolved to an http(s) URL.",
56
+ recommendation: "Set the canonical href to a valid absolute URL such as https://www.example.com/path/.",
43
57
  docs: GOOGLE_DOCS.canonical,
44
58
  }, ({ page }, make) => {
45
- const first = page.canonicals[0];
46
- if (first === undefined)
59
+ const [first] = page.canonicals;
60
+ if (first === undefined) {
47
61
  return null;
48
- return page.canonical ? null : make(`Canonical href "${first}" is not a valid URL.`, { selector: SELECTOR, evidence: { href: first } });
62
+ }
63
+ return page.canonical
64
+ ? null
65
+ : make(`Canonical href "${first}" is not a valid URL.`, {
66
+ selector: SELECTOR,
67
+ evidence: { href: first },
68
+ });
49
69
  }),
50
70
  defineRule({
51
- id: 'canonical.relative',
52
- category: 'canonical',
53
- severity: 'warning',
54
- description: 'The canonical href is a relative URL.',
55
- recommendation: 'Use an absolute URL in the canonical link. Relative canonicals are error-prone and some crawlers resolve them unexpectedly.',
71
+ id: "canonical.relative",
72
+ category: "canonical",
73
+ severity: "warning",
74
+ description: "The canonical href is a relative URL.",
75
+ recommendation: "Use an absolute URL in the canonical link. Relative canonicals are error-prone and some crawlers resolve them unexpectedly.",
56
76
  docs: GOOGLE_DOCS.canonical,
57
77
  }, ({ page }, make) => {
58
- const first = page.canonicals[0];
59
- if (!first || !page.canonical)
78
+ const [first] = page.canonicals;
79
+ if (!first || !page.canonical) {
60
80
  return null;
61
- return /^https?:\/\//i.test(first) ? null : make(`Canonical href "${first}" is relative.`, { selector: SELECTOR, evidence: { href: first, resolved: page.canonical } });
81
+ }
82
+ return /^https?:\/\//iu.test(first)
83
+ ? null
84
+ : make(`Canonical href "${first}" is relative.`, {
85
+ selector: SELECTOR,
86
+ evidence: { href: first, resolved: page.canonical },
87
+ });
62
88
  }),
63
89
  defineRule({
64
- id: 'canonical.protocol-mismatch',
65
- category: 'canonical',
66
- severity: 'warning',
67
- description: 'The page is served over https but the canonical points to http.',
68
- recommendation: 'Point the canonical at the https URL.',
90
+ id: "canonical.protocol-mismatch",
91
+ category: "canonical",
92
+ severity: "warning",
93
+ description: "The page is served over https but the canonical points to http.",
94
+ recommendation: "Point the canonical at the https URL.",
69
95
  docs: GOOGLE_DOCS.canonical,
70
96
  }, ({ page, source }, make) => {
71
- if (source !== 'url' || !page.canonical)
97
+ if (source !== "url" || !page.canonical) {
72
98
  return null;
73
- return page.url.startsWith('https://') && page.canonical.startsWith('http://')
74
- ? make('Canonical uses http:// while the page is served over https://.', { selector: SELECTOR, evidence: { canonical: page.canonical, page: page.url } })
99
+ }
100
+ return page.url.startsWith("https://") &&
101
+ page.canonical.startsWith("http://")
102
+ ? make("Canonical uses http:// while the page is served over https://.", {
103
+ selector: SELECTOR,
104
+ evidence: { canonical: page.canonical, page: page.url },
105
+ })
75
106
  : null;
76
107
  }),
77
108
  defineRule({
78
- id: 'canonical.other-page',
79
- category: 'canonical',
80
- severity: 'info',
81
- description: 'The canonical points to a different URL, so search engines will treat this page as a duplicate of that URL.',
82
- recommendation: 'If this page should rank on its own, make the canonical self-referencing. If it is intentionally a duplicate (print view, filtered list, tracking URL), this is correct.',
109
+ id: "canonical.other-page",
110
+ category: "canonical",
111
+ severity: "info",
112
+ description: "The canonical points to a different URL, so search engines will treat this page as a duplicate of that URL.",
113
+ recommendation: "If this page should rank on its own, make the canonical self-referencing. If it is intentionally a duplicate (print view, filtered list, tracking URL), this is correct.",
83
114
  docs: GOOGLE_DOCS.canonical,
84
115
  }, ({ page, source }, make) => {
85
- if (!page.canonical)
116
+ if (!page.canonical) {
86
117
  return null;
118
+ }
87
119
  const pageUrl = normalizeUrl(page.url);
88
120
  const canon = normalizeUrl(page.canonical);
89
- if (!pageUrl || !canon)
121
+ if (!pageUrl || !canon) {
90
122
  return null;
123
+ }
91
124
  // For files and local dev servers the host is synthetic; compare paths only.
92
- const pathOnly = source !== 'url' || isLocalHost(page.url);
93
- const same = pathOnly ? samePath(pageUrl, canon) : stripTrailingSlash(pageUrl) === stripTrailingSlash(canon);
125
+ const pathOnly = source !== "url" || isLocalHost(page.url);
126
+ const same = pathOnly
127
+ ? samePath(pageUrl, canon)
128
+ : stripTrailingSlash(pageUrl) === stripTrailingSlash(canon);
94
129
  return same
95
130
  ? null
96
131
  : make(`Canonical points to ${page.canonical}, not to this page.`, {
97
132
  selector: SELECTOR,
98
- evidence: { canonical: page.canonical, page: page.url, comparedBy: pathOnly ? 'path' : 'url' },
133
+ evidence: {
134
+ canonical: page.canonical,
135
+ page: page.url,
136
+ comparedBy: pathOnly ? "path" : "url",
137
+ },
99
138
  });
100
139
  }),
101
140
  ];
102
- function stripTrailingSlash(url) {
103
- return url.endsWith('/') && new URL(url).pathname !== '/' ? url.slice(0, -1) : url;
104
- }
105
141
  //# sourceMappingURL=canonical.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/core/rules/canonical.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/core/rules/canonical.ts"],"names":[],"mappings":"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"}
@@ -1,41 +1,55 @@
1
- import { fleschReadingEase, hasPlaceholderText } from '../html/text.js';
2
- import { defineRule, GOOGLE_DOCS } from './rule.js';
1
+ import { fleschReadingEase, hasPlaceholderText } from "../html/text.js";
2
+ import { defineRule, GOOGLE_DOCS } from "./rule.js";
3
3
  export const contentRules = [
4
4
  defineRule({
5
- id: 'content.empty',
6
- category: 'content',
7
- severity: 'warning',
8
- description: 'The HTML contains no visible text.',
9
- recommendation: 'If the page is rendered client-side, use server-side rendering or static generation so the HTML carries the content. Re-run with render enabled to compare the rendered DOM.',
5
+ id: "content.empty",
6
+ category: "content",
7
+ severity: "warning",
8
+ description: "The HTML contains no visible text.",
9
+ recommendation: "If the page is rendered client-side, use server-side rendering or static generation so the HTML carries the content. Re-run with render enabled to compare the rendered DOM.",
10
10
  docs: GOOGLE_DOCS.javascript,
11
- }, ({ page }, make) => (page.wordCount === 0 ? make('Page body has no visible text in the HTML response.') : null)),
11
+ }, ({ page }, make) => page.wordCount === 0
12
+ ? make("Page body has no visible text in the HTML response.")
13
+ : null),
12
14
  defineRule({
13
- id: 'content.thin',
14
- category: 'content',
15
- severity: 'warning',
16
- description: 'The page has very little text (default: fewer than 300 words).',
17
- recommendation: 'Add substantive, useful content, or set noindex if the page is a utility page (login, cart, thank-you). Thin pages rarely rank and can drag down site quality signals.',
15
+ id: "content.thin",
16
+ category: "content",
17
+ severity: "warning",
18
+ description: "The page has very little text (default: fewer than 300 words).",
19
+ recommendation: "Add substantive, useful content, or set noindex if the page is a utility page (login, cart, thank-you). Thin pages rarely rank and can drag down site quality signals.",
18
20
  }, ({ page, thresholds }, make) => page.wordCount > 0 && page.wordCount < thresholds.thinContentWords
19
- ? make(`Page has only ${page.wordCount} words of visible text (threshold ${thresholds.thinContentWords}).`, { evidence: { wordCount: page.wordCount, threshold: thresholds.thinContentWords } })
21
+ ? make(`Page has only ${page.wordCount} words of visible text (threshold ${thresholds.thinContentWords}).`, {
22
+ evidence: {
23
+ wordCount: page.wordCount,
24
+ threshold: thresholds.thinContentWords,
25
+ },
26
+ })
20
27
  : null),
21
28
  defineRule({
22
- id: 'content.placeholder-text',
23
- category: 'content',
24
- severity: 'error',
29
+ id: "content.placeholder-text",
30
+ category: "content",
31
+ severity: "error",
25
32
  description: 'Placeholder text ("lorem ipsum") is present in the page.',
26
- recommendation: 'Replace the placeholder copy with real content before publishing.',
27
- }, ({ page }, make) => (hasPlaceholderText(page.text) ? make('Page contains "lorem ipsum" placeholder text.') : null)),
33
+ recommendation: "Replace the placeholder copy with real content before publishing.",
34
+ }, ({ page }, make) => hasPlaceholderText(page.text)
35
+ ? make('Page contains "lorem ipsum" placeholder text.')
36
+ : null),
28
37
  defineRule({
29
- id: 'content.readability-low',
30
- category: 'content',
31
- severity: 'info',
32
- description: 'Flesch Reading Ease below 30 (very difficult). Only evaluated for English or unspecified language, 100+ words.',
33
- recommendation: 'Shorten sentences and prefer common words. Aim for a Flesch score of 50-70 for general audiences.',
38
+ id: "content.readability-low",
39
+ category: "content",
40
+ severity: "info",
41
+ description: "Flesch Reading Ease below 30 (very difficult). Only evaluated for English or unspecified language, 100+ words.",
42
+ recommendation: "Shorten sentences and prefer common words. Aim for a Flesch score of 50-70 for general audiences.",
34
43
  }, ({ page }, make) => {
35
- if (page.lang && !page.lang.toLowerCase().startsWith('en'))
44
+ if (page.lang && !page.lang.toLowerCase().startsWith("en")) {
36
45
  return null;
46
+ }
37
47
  const score = fleschReadingEase(page.text);
38
- return score !== null && score < 30 ? make(`Flesch Reading Ease is ${score} (very difficult).`, { evidence: { fleschReadingEase: score, wordCount: page.wordCount } }) : null;
48
+ return score !== null && score < 30
49
+ ? make(`Flesch Reading Ease is ${score} (very difficult).`, {
50
+ evidence: { fleschReadingEase: score, wordCount: page.wordCount },
51
+ })
52
+ : null;
39
53
  }),
40
54
  ];
41
55
  //# sourceMappingURL=content.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/core/rules/content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,CACR;QACE,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,oCAAoC;QACjD,cAAc,EAAE,8KAA8K;QAC9L,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAChH;IACD,UAAU,CACR;QACE,EAAE,EAAE,cAAc;QAClB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,gEAAgE;QAC7E,cAAc,EAAE,wKAAwK;KACzL,EACD,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,CAC7B,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,gBAAgB;QAChE,CAAC,CAAC,IAAI,CAAC,iBAAiB,IAAI,CAAC,SAAS,qCAAqC,UAAU,CAAC,gBAAgB,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAChM,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,0DAA0D;QACvE,cAAc,EAAE,mEAAmE;KACpF,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CACnH;IACD,UAAU,CACR;QACE,EAAE,EAAE,yBAAyB;QAC7B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,gHAAgH;QAC7H,cAAc,EAAE,mGAAmG;KACpH,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,0BAA0B,KAAK,oBAAoB,EAAE,EAAE,QAAQ,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChL,CAAC,CACF;CACF,CAAC"}
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/core/rules/content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,UAAU,CACR;QACE,EAAE,EAAE,eAAe;QACnB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,oCAAoC;QACjD,cAAc,EACZ,8KAA8K;QAChL,IAAI,EAAE,WAAW,CAAC,UAAU;KAC7B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,IAAI,CAAC,SAAS,KAAK,CAAC;QAClB,CAAC,CAAC,IAAI,CAAC,qDAAqD,CAAC;QAC7D,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,cAAc;QAClB,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,gEAAgE;QAClE,cAAc,EACZ,wKAAwK;KAC3K,EACD,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,CAC7B,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,gBAAgB;QAChE,CAAC,CAAC,IAAI,CACF,iBAAiB,IAAI,CAAC,SAAS,qCAAqC,UAAU,CAAC,gBAAgB,IAAI,EACnG;YACE,QAAQ,EAAE;gBACR,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,SAAS,EAAE,UAAU,CAAC,gBAAgB;aACvC;SACF,CACF;QACH,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,0BAA0B;QAC9B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,0DAA0D;QACvE,cAAc,EACZ,mEAAmE;KACtE,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,CAAC,CAAC,IAAI,CAAC,+CAA+C,CAAC;QACvD,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,yBAAyB;QAC7B,QAAQ,EAAE,SAAS;QACnB,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,gHAAgH;QAClH,cAAc,EACZ,mGAAmG;KACtG,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE3C,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,EAAE;YACjC,CAAC,CAAC,IAAI,CAAC,0BAA0B,KAAK,oBAAoB,EAAE;gBACxD,QAAQ,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE;aAClE,CAAC;YACJ,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;CACF,CAAC"}
@@ -1,60 +1,83 @@
1
- import { defineRule, GOOGLE_DOCS } from './rule.js';
2
- const BCP47 = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
1
+ import { defineRule, GOOGLE_DOCS } from "./rule.js";
2
+ const BCP47 = /^[a-zA-Z]{2,3}(?:-[a-zA-Z0-9]{2,8})*$/u;
3
3
  export const documentRules = [
4
4
  defineRule({
5
- id: 'html.lang-missing',
6
- category: 'document',
7
- severity: 'warning',
8
- description: 'The <html> element has no lang attribute.',
5
+ id: "html.lang-missing",
6
+ category: "document",
7
+ severity: "warning",
8
+ description: "The <html> element has no lang attribute.",
9
9
  recommendation: 'Set <html lang="xx"> (for example "en", "nl", "en-GB") so search engines and screen readers know the content language.',
10
- }, ({ page }, make) => (page.lang ? null : make('<html> has no lang attribute.', { selector: 'html' }))),
10
+ }, ({ page }, make) => page.lang
11
+ ? null
12
+ : make("<html> has no lang attribute.", { selector: "html" })),
11
13
  defineRule({
12
- id: 'html.lang-invalid',
13
- category: 'document',
14
- severity: 'warning',
15
- description: 'The <html lang> value is not a valid BCP 47 language tag.',
14
+ id: "html.lang-invalid",
15
+ category: "document",
16
+ severity: "warning",
17
+ description: "The <html lang> value is not a valid BCP 47 language tag.",
16
18
  recommendation: 'Use a BCP 47 tag such as "en", "en-US" or "pt-BR".',
17
- }, ({ page }, make) => (page.lang && !BCP47.test(page.lang) ? make(`<html lang="${page.lang}"> is not a valid language tag.`, { selector: 'html', evidence: { lang: page.lang } }) : null)),
19
+ }, ({ page }, make) => page.lang && !BCP47.test(page.lang)
20
+ ? make(`<html lang="${page.lang}"> is not a valid language tag.`, {
21
+ selector: "html",
22
+ evidence: { lang: page.lang },
23
+ })
24
+ : null),
18
25
  defineRule({
19
- id: 'viewport.missing',
20
- category: 'document',
21
- severity: 'warning',
26
+ id: "viewport.missing",
27
+ category: "document",
28
+ severity: "warning",
22
29
  description: 'No <meta name="viewport"> is present, so the page is not mobile-friendly.',
23
30
  recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1"> to <head>. Google indexes the mobile version of pages first.',
24
31
  docs: GOOGLE_DOCS.mobile,
25
- }, ({ page }, make) => (page.viewport ? null : make('Page has no viewport meta tag.', { selector: 'head > meta[name="viewport"]' }))),
32
+ }, ({ page }, make) => page.viewport
33
+ ? null
34
+ : make("Page has no viewport meta tag.", {
35
+ selector: 'head > meta[name="viewport"]',
36
+ })),
26
37
  defineRule({
27
- id: 'viewport.not-scalable',
28
- category: 'document',
29
- severity: 'info',
30
- description: 'The viewport disables user zoom (user-scalable=no or maximum-scale=1).',
31
- recommendation: 'Remove user-scalable=no and maximum-scale limits; blocking zoom is an accessibility problem flagged by Lighthouse.',
38
+ id: "viewport.not-scalable",
39
+ category: "document",
40
+ severity: "info",
41
+ description: "The viewport disables user zoom (user-scalable=no or maximum-scale=1).",
42
+ recommendation: "Remove user-scalable=no and maximum-scale limits; blocking zoom is an accessibility problem flagged by Lighthouse.",
32
43
  }, ({ page }, make) => {
33
- const v = (page.viewport ?? '').toLowerCase().replace(/\s/g, '');
34
- return /user-scalable=(no|0)/.test(v) || /maximum-scale=1(\.0*)?(,|$)/.test(v)
35
- ? make('Viewport prevents users from zooming.', { selector: 'head > meta[name="viewport"]', evidence: { viewport: page.viewport } })
44
+ const v = (page.viewport ?? "").toLowerCase().replaceAll(/\s/gu, "");
45
+ return /user-scalable=(?:no|0)/u.test(v) ||
46
+ /maximum-scale=1(?:\.0*)?(?:,|$)/u.test(v)
47
+ ? make("Viewport prevents users from zooming.", {
48
+ selector: 'head > meta[name="viewport"]',
49
+ evidence: { viewport: page.viewport },
50
+ })
36
51
  : null;
37
52
  }),
38
53
  defineRule({
39
- id: 'charset.missing',
40
- category: 'document',
41
- severity: 'info',
42
- description: 'No character encoding is declared.',
54
+ id: "charset.missing",
55
+ category: "document",
56
+ severity: "info",
57
+ description: "No character encoding is declared.",
43
58
  recommendation: 'Add <meta charset="utf-8"> as the first element in <head>.',
44
- }, ({ page }, make) => (page.charset ? null : make('No <meta charset> declaration found.', { selector: 'head > meta[charset]' }))),
59
+ }, ({ page }, make) => page.charset
60
+ ? null
61
+ : make("No <meta charset> declaration found.", {
62
+ selector: "head > meta[charset]",
63
+ })),
45
64
  defineRule({
46
- id: 'doctype.missing',
47
- category: 'document',
48
- severity: 'info',
49
- description: 'The document does not start with <!DOCTYPE html>.',
50
- recommendation: 'Start the document with <!DOCTYPE html> to avoid quirks-mode rendering.',
51
- }, ({ page }, make) => (page.hasDoctype ? null : make('Document has no HTML5 doctype.'))),
65
+ id: "doctype.missing",
66
+ category: "document",
67
+ severity: "info",
68
+ description: "The document does not start with <!DOCTYPE html>.",
69
+ recommendation: "Start the document with <!DOCTYPE html> to avoid quirks-mode rendering.",
70
+ }, ({ page }, make) => page.hasDoctype ? null : make("Document has no HTML5 doctype.")),
52
71
  defineRule({
53
- id: 'document.html-too-large',
54
- category: 'document',
55
- severity: 'info',
56
- description: 'The HTML document is larger than 1.5 MB.',
57
- recommendation: 'Reduce inline scripts/styles/data (large JSON state, inline SVG sprites). Very large documents slow parsing and may be truncated by crawlers.',
58
- }, ({ page }, make) => page.htmlBytes > 1_500_000 ? make(`HTML is ${(page.htmlBytes / 1_000_000).toFixed(1)} MB.`, { evidence: { bytes: page.htmlBytes } }) : null),
72
+ id: "document.html-too-large",
73
+ category: "document",
74
+ severity: "info",
75
+ description: "The HTML document is larger than 1.5 MB.",
76
+ recommendation: "Reduce inline scripts/styles/data (large JSON state, inline SVG sprites). Very large documents slow parsing and may be truncated by crawlers.",
77
+ }, ({ page }, make) => page.htmlBytes > 1_500_000
78
+ ? make(`HTML is ${(page.htmlBytes / 1_000_000).toFixed(1)} MB.`, {
79
+ evidence: { bytes: page.htmlBytes },
80
+ })
81
+ : null),
59
82
  ];
60
83
  //# sourceMappingURL=document.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"document.js","sourceRoot":"","sources":["../../../src/core/rules/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpD,MAAM,KAAK,GAAG,qCAAqC,CAAC;AAEpD,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,UAAU,CACR;QACE,EAAE,EAAE,mBAAmB;QACvB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,2CAA2C;QACxD,cAAc,EAAE,wHAAwH;KACzI,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,CACrG;IACD,UAAU,CACR;QACE,EAAE,EAAE,mBAAmB;QACvB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,2DAA2D;QACxE,cAAc,EAAE,oDAAoD;KACrE,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,iCAAiC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CACxL;IACD,UAAU,CACR;QACE,EAAE,EAAE,kBAAkB;QACtB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,2EAA2E;QACxF,cAAc,EAAE,uIAAuI;QACvJ,IAAI,EAAE,WAAW,CAAC,MAAM;KACzB,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,gCAAgC,EAAE,EAAE,QAAQ,EAAE,8BAA8B,EAAE,CAAC,CAAC,CAClI;IACD,UAAU,CACR;QACE,EAAE,EAAE,uBAAuB;QAC3B,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,wEAAwE;QACrF,cAAc,EAAE,oHAAoH;KACrI,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjE,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,6BAA6B,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5E,CAAC,CAAC,IAAI,CAAC,uCAAuC,EAAE,EAAE,QAAQ,EAAE,8BAA8B,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YACpI,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,oCAAoC;QACjD,cAAc,EAAE,4DAA4D;KAC7E,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,sCAAsC,EAAE,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC,CAAC,CAC/H;IACD,UAAU,CACR;QACE,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,mDAAmD;QAChE,cAAc,EAAE,yEAAyE;KAC1F,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CACtF;IACD,UAAU,CACR;QACE,EAAE,EAAE,yBAAyB;QAC7B,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,MAAM;QAChB,WAAW,EAAE,0CAA0C;QACvD,cAAc,EAAE,+IAA+I;KAChK,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9I;CACF,CAAC"}
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../../src/core/rules/document.ts"],"names":[],"mappings":"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"}
@@ -1,61 +1,81 @@
1
- import { cap, defineRule } from './rule.js';
1
+ import { cap, defineRule } from "./rule.js";
2
2
  export const headingRules = [
3
3
  defineRule({
4
- id: 'h1.missing',
5
- category: 'headings',
6
- severity: 'warning',
7
- description: 'The page has no <h1>.',
8
- recommendation: 'Add one <h1> that states the main topic of the page. It is usually the visible page heading.',
9
- }, ({ page }, make) => (page.h1s.length === 0 ? make('Page has no <h1> heading.', { selector: 'h1' }) : null)),
4
+ id: "h1.missing",
5
+ category: "headings",
6
+ severity: "warning",
7
+ description: "The page has no <h1>.",
8
+ recommendation: "Add one <h1> that states the main topic of the page. It is usually the visible page heading.",
9
+ }, ({ page }, make) => page.h1s.length === 0
10
+ ? make("Page has no <h1> heading.", { selector: "h1" })
11
+ : null),
10
12
  defineRule({
11
- id: 'h1.multiple',
12
- category: 'headings',
13
- severity: 'info',
14
- description: 'The page has more than one <h1>.',
15
- recommendation: 'Prefer a single <h1> for the main topic and use <h2>-<h6> for sections. Multiple H1s are valid HTML but make the main topic less clear.',
16
- }, ({ page }, make) => page.h1s.length > 1 ? make(`Page has ${page.h1s.length} <h1> headings.`, { selector: 'h1', evidence: { h1s: cap(page.h1s) } }) : null),
13
+ id: "h1.multiple",
14
+ category: "headings",
15
+ severity: "info",
16
+ description: "The page has more than one <h1>.",
17
+ recommendation: "Prefer a single <h1> for the main topic and use <h2>-<h6> for sections. Multiple H1s are valid HTML but make the main topic less clear.",
18
+ }, ({ page }, make) => page.h1s.length > 1
19
+ ? make(`Page has ${page.h1s.length} <h1> headings.`, {
20
+ selector: "h1",
21
+ evidence: { h1s: cap(page.h1s) },
22
+ })
23
+ : null),
17
24
  defineRule({
18
- id: 'h1.empty',
19
- category: 'headings',
20
- severity: 'warning',
21
- description: 'An <h1> exists but contains no text.',
22
- recommendation: 'Put the page heading text inside the <h1>. If it wraps only an image, give the image descriptive alt text.',
23
- }, ({ page }, make) => (page.h1s.length > 0 && page.h1s.every((h) => !h) ? make('All <h1> elements are empty.', { selector: 'h1' }) : null)),
25
+ id: "h1.empty",
26
+ category: "headings",
27
+ severity: "warning",
28
+ description: "An <h1> exists but contains no text.",
29
+ recommendation: "Put the page heading text inside the <h1>. If it wraps only an image, give the image descriptive alt text.",
30
+ }, ({ page }, make) => page.h1s.length > 0 && page.h1s.every((h) => !h)
31
+ ? make("All <h1> elements are empty.", { selector: "h1" })
32
+ : null),
24
33
  defineRule({
25
- id: 'h1.too-long',
26
- category: 'headings',
27
- severity: 'info',
28
- description: 'The <h1> is longer than the configured maximum (default 70 characters).',
29
- recommendation: 'Keep the H1 concise; move detail into the intro paragraph.',
34
+ id: "h1.too-long",
35
+ category: "headings",
36
+ severity: "info",
37
+ description: "The <h1> is longer than the configured maximum (default 70 characters).",
38
+ recommendation: "Keep the H1 concise; move detail into the intro paragraph.",
30
39
  }, ({ page, thresholds }, make) => {
31
40
  const long = page.h1s.filter((h) => h.length > thresholds.h1MaxChars);
32
- return long.length ? make(`${long.length} <h1> heading(s) exceed ${thresholds.h1MaxChars} characters.`, { selector: 'h1', evidence: { h1s: cap(long) } }) : null;
41
+ return long.length
42
+ ? make(`${long.length} <h1> heading(s) exceed ${thresholds.h1MaxChars} characters.`, { selector: "h1", evidence: { h1s: cap(long) } })
43
+ : null;
33
44
  }),
34
45
  defineRule({
35
- id: 'headings.empty',
36
- category: 'headings',
37
- severity: 'warning',
38
- description: 'Heading elements (h2-h6) with no text content.',
39
- recommendation: 'Remove empty heading tags or fill them with text. Empty headings confuse outline parsers and screen readers.',
46
+ id: "headings.empty",
47
+ category: "headings",
48
+ severity: "warning",
49
+ description: "Heading elements (h2-h6) with no text content.",
50
+ recommendation: "Remove empty heading tags or fill them with text. Empty headings confuse outline parsers and screen readers.",
40
51
  }, ({ page }, make) => {
41
52
  const empty = page.headings.filter((h) => h.level > 1 && !h.text);
42
- return empty.length ? make(`${empty.length} empty heading element(s) found.`, { evidence: { levels: cap(empty.map((h) => `h${h.level}`)) } }) : null;
53
+ return empty.length
54
+ ? make(`${empty.length} empty heading element(s) found.`, {
55
+ evidence: { levels: cap(empty.map((h) => `h${h.level}`)) },
56
+ })
57
+ : null;
43
58
  }),
44
59
  defineRule({
45
- id: 'headings.skipped-level',
46
- category: 'headings',
47
- severity: 'info',
48
- description: 'Heading levels are skipped (for example an <h2> followed directly by an <h4>).',
49
- recommendation: 'Use heading levels in order (h1 > h2 > h3) so the document outline reflects the content structure.',
60
+ id: "headings.skipped-level",
61
+ category: "headings",
62
+ severity: "info",
63
+ description: "Heading levels are skipped (for example an <h2> followed directly by an <h4>).",
64
+ recommendation: "Use heading levels in order (h1 > h2 > h3) so the document outline reflects the content structure.",
50
65
  }, ({ page }, make) => {
51
66
  const skips = [];
52
67
  let prev = 0;
53
68
  for (const h of page.headings) {
54
- if (prev > 0 && h.level > prev + 1)
69
+ if (prev > 0 && h.level > prev + 1) {
55
70
  skips.push(`h${prev} → h${h.level} ("${h.text.slice(0, 40)}")`);
71
+ }
56
72
  prev = h.level;
57
73
  }
58
- return skips.length ? make(`${skips.length} heading level skip(s) found.`, { evidence: { skips: cap(skips) } }) : null;
74
+ return skips.length
75
+ ? make(`${skips.length} heading level skip(s) found.`, {
76
+ evidence: { skips: cap(skips) },
77
+ })
78
+ : null;
59
79
  }),
60
80
  ];
61
81
  //# sourceMappingURL=headings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"headings.js","sourceRoot":"","sources":["../../../src/core/rules/headings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"headings.js","sourceRoot":"","sources":["../../../src/core/rules/headings.ts"],"names":[],"mappings":"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"}