@rivi-software/agentic-seo-check 0.1.1 → 0.3.0
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 +67 -7
- package/dist/cli.js +69 -26
- package/dist/cli.js.map +1 -1
- package/dist/core/aeo/audit.d.ts +44 -0
- package/dist/core/aeo/audit.js +310 -0
- package/dist/core/aeo/audit.js.map +1 -0
- package/dist/core/aeo/bots.d.ts +30 -0
- package/dist/core/aeo/bots.js +299 -0
- package/dist/core/aeo/bots.js.map +1 -0
- package/dist/core/aeo/llms-txt.d.ts +8 -0
- package/dist/core/aeo/llms-txt.js +88 -0
- package/dist/core/aeo/llms-txt.js.map +1 -0
- package/dist/core/aeo/page.d.ts +43 -0
- package/dist/core/aeo/page.js +284 -0
- package/dist/core/aeo/page.js.map +1 -0
- package/dist/core/aeo/probe.d.ts +10 -0
- package/dist/core/aeo/probe.js +53 -0
- package/dist/core/aeo/probe.js.map +1 -0
- package/dist/core/aeo/site-rules.d.ts +13 -0
- package/dist/core/aeo/site-rules.js +178 -0
- package/dist/core/aeo/site-rules.js.map +1 -0
- package/dist/core/audit.d.ts +23 -2
- package/dist/core/audit.js +50 -22
- package/dist/core/audit.js.map +1 -1
- package/dist/core/crawl.d.ts +7 -1
- package/dist/core/crawl.js +85 -9
- package/dist/core/crawl.js.map +1 -1
- package/dist/core/fetch.d.ts +9 -0
- package/dist/core/fetch.js +28 -12
- package/dist/core/fetch.js.map +1 -1
- package/dist/core/html/answer.d.ts +4 -0
- package/dist/core/html/answer.js +198 -0
- package/dist/core/html/answer.js.map +1 -0
- package/dist/core/html/dom.d.ts +6 -0
- package/dist/core/html/dom.js +92 -1
- package/dist/core/html/dom.js.map +1 -1
- package/dist/core/html/extract.js +21 -72
- package/dist/core/html/extract.js.map +1 -1
- package/dist/core/html/head.d.ts +10 -0
- package/dist/core/html/head.js +206 -0
- package/dist/core/html/head.js.map +1 -0
- package/dist/core/html/resources.d.ts +17 -0
- package/dist/core/html/resources.js +172 -0
- package/dist/core/html/resources.js.map +1 -0
- package/dist/core/html/structured-data.d.ts +2 -0
- package/dist/core/html/structured-data.js +2 -0
- package/dist/core/html/structured-data.js.map +1 -1
- package/dist/core/report/markdown.d.ts +3 -1
- package/dist/core/report/markdown.js +102 -0
- package/dist/core/report/markdown.js.map +1 -1
- package/dist/core/robots.d.ts +7 -0
- package/dist/core/robots.js +13 -1
- package/dist/core/robots.js.map +1 -1
- package/dist/core/rules/aeo.d.ts +11 -0
- package/dist/core/rules/aeo.js +423 -0
- package/dist/core/rules/aeo.js.map +1 -0
- package/dist/core/rules/canonical.d.ts +3 -0
- package/dist/core/rules/canonical.js +54 -2
- package/dist/core/rules/canonical.js.map +1 -1
- package/dist/core/rules/document.js +4 -2
- package/dist/core/rules/document.js.map +1 -1
- package/dist/core/rules/head.d.ts +1 -0
- package/dist/core/rules/head.js +116 -0
- package/dist/core/rules/head.js.map +1 -0
- package/dist/core/rules/index.d.ts +4 -0
- package/dist/core/rules/index.js +15 -1
- package/dist/core/rules/index.js.map +1 -1
- package/dist/core/rules/indexing.d.ts +10 -0
- package/dist/core/rules/indexing.js +171 -9
- package/dist/core/rules/indexing.js.map +1 -1
- package/dist/core/rules/loading.d.ts +1 -0
- package/dist/core/rules/loading.js +138 -0
- package/dist/core/rules/loading.js.map +1 -0
- package/dist/core/rules/rule.d.ts +2 -0
- package/dist/core/rules/rule.js +2 -0
- package/dist/core/rules/rule.js.map +1 -1
- package/dist/core/rules/structured-data-values.d.ts +3 -0
- package/dist/core/rules/structured-data-values.js +316 -0
- package/dist/core/rules/structured-data-values.js.map +1 -0
- package/dist/core/rules/structured-data.js +50 -20
- package/dist/core/rules/structured-data.js.map +1 -1
- package/dist/core/rules/urls.d.ts +1 -0
- package/dist/core/rules/urls.js +293 -0
- package/dist/core/rules/urls.js.map +1 -0
- package/dist/core/score.d.ts +4 -2
- package/dist/core/score.js.map +1 -1
- package/dist/core/site-probes.d.ts +33 -0
- package/dist/core/site-probes.js +144 -0
- package/dist/core/site-probes.js.map +1 -0
- package/dist/core/site-rules.d.ts +18 -0
- package/dist/core/site-rules.js +479 -5
- package/dist/core/site-rules.js.map +1 -1
- package/dist/core/sitemap.d.ts +38 -1
- package/dist/core/sitemap.js +177 -43
- package/dist/core/sitemap.js.map +1 -1
- package/dist/core/static.d.ts +6 -0
- package/dist/core/static.js +122 -12
- package/dist/core/static.js.map +1 -1
- package/dist/core/types.d.ts +268 -2
- package/dist/core/types.js.map +1 -1
- package/dist/core/url.d.ts +2 -0
- package/dist/core/url.js +8 -0
- package/dist/core/url.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +129 -3
- package/dist/mcp.js.map +1 -1
- package/package.json +6 -2
- package/skills/seo-audit/SKILL.md +9 -2
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { articles, comparable, directivesFor, hasNoindex, hasNosnippet, isEntity, isHomePage, markedUpTexts, maxSnippet, nameOf, stripTags, typedNodes, } from "../aeo/page.js";
|
|
2
|
+
import { isJsonObject, isString } from "../html/structured-data.js";
|
|
3
|
+
import { parseRobotsHeader, robotsDirectives } from "./indexing.js";
|
|
4
|
+
import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
5
|
+
/** Answer engines' own documentation for the AEO rules. */
|
|
6
|
+
export const AEO_DOCS = {
|
|
7
|
+
googleAi: "https://developers.google.com/search/docs/appearance/ai-features",
|
|
8
|
+
bing: "https://www.bing.com/webmasters/help/webmaster-guidelines-30fba23a",
|
|
9
|
+
httpErrors: "https://developers.google.com/search/docs/crawling-indexing/http-network-errors",
|
|
10
|
+
structuredDataPolicies: "https://developers.google.com/search/docs/appearance/structured-data/sd-policies",
|
|
11
|
+
article: "https://developers.google.com/search/docs/appearance/structured-data/article",
|
|
12
|
+
dates: "https://developers.google.com/search/docs/appearance/publication-dates",
|
|
13
|
+
organization: "https://developers.google.com/search/docs/appearance/structured-data/organization",
|
|
14
|
+
};
|
|
15
|
+
/** A question answered in more words than this does not start with a direct answer. */
|
|
16
|
+
const LONG_ANSWER_WORDS = 100;
|
|
17
|
+
const sourcesOf = (meta, header, directive) => {
|
|
18
|
+
const sources = [];
|
|
19
|
+
for (const m of meta) {
|
|
20
|
+
if (m.content.split(",").some((d) => directive(d.replaceAll(/\s+/gu, "")))) {
|
|
21
|
+
sources.push(`<meta name="${m.name}">`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (header && parseRobotsHeader(header).some((d) => directive(d.directive))) {
|
|
25
|
+
sources.push("X-Robots-Tag");
|
|
26
|
+
}
|
|
27
|
+
return [...new Set(sources)];
|
|
28
|
+
};
|
|
29
|
+
const metaSelector = (sources) => {
|
|
30
|
+
const meta = sources.find((s) => s.startsWith("<meta"));
|
|
31
|
+
return meta
|
|
32
|
+
? `head > meta[name="${meta.slice('<meta name="'.length, -2)}"]`
|
|
33
|
+
: undefined;
|
|
34
|
+
};
|
|
35
|
+
const isNoai = (d) => d === "noai" || d === "noimageai";
|
|
36
|
+
const yearOf = (value) => isString(value) ? (/^\d{4}/u.exec(value.trim())?.[0] ?? null) : null;
|
|
37
|
+
export const aeoRules = [
|
|
38
|
+
defineRule({
|
|
39
|
+
id: "aeo.page-error",
|
|
40
|
+
category: "aeo",
|
|
41
|
+
severity: "error",
|
|
42
|
+
description: "The page answers with an error status or could not be fetched, so no crawler can read it.",
|
|
43
|
+
recommendation: "Make the URL answer 200 with the page's HTML. Answer engines, like search engines, drop pages that fail.",
|
|
44
|
+
docs: AEO_DOCS.httpErrors,
|
|
45
|
+
}, ({ response }, make) => {
|
|
46
|
+
if (!response) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (response.error) {
|
|
50
|
+
return make(`The page could not be fetched: ${response.error}.`, {
|
|
51
|
+
evidence: { error: response.error },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return response.status !== null && response.status >= 400
|
|
55
|
+
? make(`The page answers HTTP ${response.status}.`, {
|
|
56
|
+
evidence: { status: response.status },
|
|
57
|
+
})
|
|
58
|
+
: null;
|
|
59
|
+
}),
|
|
60
|
+
defineRule({
|
|
61
|
+
id: "aeo.noindex",
|
|
62
|
+
category: "aeo",
|
|
63
|
+
severity: "warning",
|
|
64
|
+
description: "The page is set to noindex, so answer engines that draw on a search index cannot cite it.",
|
|
65
|
+
recommendation: "Remove noindex if answer engines should quote this page. Keep it on pages that should stay private (login, cart, internal search); preview hosts often add it with X-Robots-Tag, so check production first.",
|
|
66
|
+
docs: AEO_DOCS.googleAi,
|
|
67
|
+
}, ({ page, response }, make) => {
|
|
68
|
+
const r = robotsDirectives(page, response);
|
|
69
|
+
return r.noindex
|
|
70
|
+
? make(`Page is set to noindex via ${r.sources.join(" and ")}, so Google AI Overviews and AI Mode, Copilot and other answer engines built on a search index cannot cite it.`, {
|
|
71
|
+
selector: r.sources.includes("meta")
|
|
72
|
+
? 'head > meta[name="robots"]'
|
|
73
|
+
: undefined,
|
|
74
|
+
evidence: { directives: r.directives, sources: r.sources },
|
|
75
|
+
})
|
|
76
|
+
: null;
|
|
77
|
+
}),
|
|
78
|
+
defineRule({
|
|
79
|
+
id: "aeo.nosnippet",
|
|
80
|
+
category: "aeo",
|
|
81
|
+
severity: "warning",
|
|
82
|
+
description: "nosnippet (or max-snippet:0) keeps the page's text out of Google's AI Overviews and AI Mode and limits what Copilot can cite.",
|
|
83
|
+
recommendation: "Remove nosnippet or max-snippet:0 if answer engines should quote this page. To hide one part only, wrap that part in an element with data-nosnippet.",
|
|
84
|
+
docs: AEO_DOCS.googleAi,
|
|
85
|
+
}, ({ page, response }, make) => {
|
|
86
|
+
const google = directivesFor("google", page, response);
|
|
87
|
+
const bing = directivesFor("bing", page, response);
|
|
88
|
+
const engines = [
|
|
89
|
+
...(hasNosnippet(google) && !hasNoindex(google) ? ["Google"] : []),
|
|
90
|
+
...(hasNosnippet(bing) && !hasNoindex(bing) ? ["Bing"] : []),
|
|
91
|
+
];
|
|
92
|
+
if (!engines.length) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const sources = sourcesOf(page.answer.robotsMeta, response?.headers["x-robots-tag"], (d) => d === "nosnippet" || d === "max-snippet:0");
|
|
96
|
+
return make(`Page sets nosnippet for ${engines.join(" and ")} via ${sources.join(" and ")}, so its text cannot be quoted in AI answers.`, {
|
|
97
|
+
selector: metaSelector(sources),
|
|
98
|
+
evidence: { engines, sources },
|
|
99
|
+
});
|
|
100
|
+
}),
|
|
101
|
+
defineRule({
|
|
102
|
+
id: "aeo.max-snippet-short",
|
|
103
|
+
category: "aeo",
|
|
104
|
+
severity: "info",
|
|
105
|
+
description: "max-snippet is below 100 characters, which also caps what Google quotes in AI Overviews and AI Mode.",
|
|
106
|
+
recommendation: "Raise max-snippet, or set max-snippet:-1 for no limit, so answer engines can quote whole sentences.",
|
|
107
|
+
docs: GOOGLE_DOCS.robotsMeta,
|
|
108
|
+
}, ({ page, response }, make) => {
|
|
109
|
+
const directives = directivesFor("google", page, response);
|
|
110
|
+
const limit = maxSnippet(directives);
|
|
111
|
+
return limit !== null &&
|
|
112
|
+
limit > 0 &&
|
|
113
|
+
limit < 100 &&
|
|
114
|
+
!hasNoindex(directives)
|
|
115
|
+
? make(`max-snippet:${limit} lets Google quote at most ${limit} characters of this page.`, { evidence: { maxSnippet: limit } })
|
|
116
|
+
: null;
|
|
117
|
+
}),
|
|
118
|
+
defineRule({
|
|
119
|
+
id: "aeo.noarchive",
|
|
120
|
+
category: "aeo",
|
|
121
|
+
severity: "warning",
|
|
122
|
+
description: "noarchive keeps the page out of Microsoft Copilot answers; Bing still lists it in search.",
|
|
123
|
+
recommendation: "Remove noarchive if the page should appear in Copilot answers. Paywalled pages can use nocache instead, which lets Copilot cite the URL, title and snippet. Google ignores noarchive.",
|
|
124
|
+
docs: AEO_DOCS.bing,
|
|
125
|
+
}, ({ page, response }, make) => {
|
|
126
|
+
const bing = directivesFor("bing", page, response);
|
|
127
|
+
// Bing treats a page with both noarchive and nocache as nocache.
|
|
128
|
+
if (!bing.includes("noarchive") ||
|
|
129
|
+
bing.includes("nocache") ||
|
|
130
|
+
hasNoindex(bing)) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const sources = sourcesOf(page.answer.robotsMeta.filter((m) => m.name !== "googlebot"), response?.headers["x-robots-tag"], (d) => d === "noarchive");
|
|
134
|
+
return make(`Page sets noarchive via ${sources.join(" and ")}, so Copilot leaves it out of its answers.`, { selector: metaSelector(sources), evidence: { sources } });
|
|
135
|
+
}),
|
|
136
|
+
defineRule({
|
|
137
|
+
id: "aeo.nocache",
|
|
138
|
+
category: "aeo",
|
|
139
|
+
severity: "info",
|
|
140
|
+
description: "nocache limits Microsoft Copilot to the page's URL, title and snippet.",
|
|
141
|
+
recommendation: "Remove nocache if Copilot should quote the page's content, not only link to it.",
|
|
142
|
+
docs: AEO_DOCS.bing,
|
|
143
|
+
}, ({ page, response }, make) => {
|
|
144
|
+
const bing = directivesFor("bing", page, response);
|
|
145
|
+
return bing.includes("nocache") && !hasNoindex(bing)
|
|
146
|
+
? make("Page sets nocache, so Copilot may use only its URL, title and snippet.")
|
|
147
|
+
: null;
|
|
148
|
+
}),
|
|
149
|
+
defineRule({
|
|
150
|
+
id: "aeo.noai",
|
|
151
|
+
category: "aeo",
|
|
152
|
+
severity: "info",
|
|
153
|
+
description: "The page sets noai or noimageai, directives no major answer engine documents support for.",
|
|
154
|
+
recommendation: "To opt out of AI training, say so in robots.txt (a `User-Agent: __usage_ai-training` group with `Disallow: /`, or groups for GPTBot, ClaudeBot and Google-Extended). If the page should appear in AI answers, remove noai to avoid mixed signals.",
|
|
155
|
+
}, ({ page, response }, make) => {
|
|
156
|
+
const sources = sourcesOf(page.answer.robotsMeta, response?.headers["x-robots-tag"], isNoai);
|
|
157
|
+
return sources.length
|
|
158
|
+
? make(`Page sets noai/noimageai via ${sources.join(" and ")}; Google, Bing, OpenAI and Anthropic do not document support for it.`, { selector: metaSelector(sources), evidence: { sources } })
|
|
159
|
+
: null;
|
|
160
|
+
}),
|
|
161
|
+
defineRule({
|
|
162
|
+
id: "aeo.data-nosnippet",
|
|
163
|
+
category: "aeo",
|
|
164
|
+
severity: "info",
|
|
165
|
+
description: "data-nosnippet hides a large share of the page's text (20% or more) from snippets and AI Overviews.",
|
|
166
|
+
recommendation: "Keep data-nosnippet on boilerplate or paywalled parts only. The text answer engines should quote must be outside it.",
|
|
167
|
+
docs: GOOGLE_DOCS.robotsMeta,
|
|
168
|
+
}, ({ page }, make) => {
|
|
169
|
+
const { nosnippetWords, nosnippetElements, nosnippetSelectors } = page.answer;
|
|
170
|
+
if (!nosnippetWords || page.wordCount < 50) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const share = nosnippetWords / page.wordCount;
|
|
174
|
+
if (share < 0.2) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return make(`data-nosnippet hides ${nosnippetWords} of ${page.wordCount} words (${Math.round(share * 100)}%) from snippets and AI Overviews.`, {
|
|
178
|
+
severity: share >= 0.5 ? "warning" : "info",
|
|
179
|
+
selector: nosnippetSelectors[0],
|
|
180
|
+
evidence: {
|
|
181
|
+
elements: nosnippetElements,
|
|
182
|
+
words: nosnippetWords,
|
|
183
|
+
selectors: cap(nosnippetSelectors),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
}),
|
|
187
|
+
defineRule({
|
|
188
|
+
id: "aeo.text-needs-javascript",
|
|
189
|
+
category: "aeo",
|
|
190
|
+
severity: "warning",
|
|
191
|
+
description: "The HTML is an empty JavaScript app shell; most AI crawlers do not run JavaScript, so they see no content.",
|
|
192
|
+
recommendation: "Render the content on the server (SSR or static generation) so the HTML carries it. `agentic-seo-check audit <url> --render` compares the HTML with the rendered page.",
|
|
193
|
+
docs: GOOGLE_DOCS.javascript,
|
|
194
|
+
}, ({ page }, make) => {
|
|
195
|
+
const { emptyAppRoot, noscriptAsksForJavaScript } = page.answer;
|
|
196
|
+
if (page.wordCount >= 50 ||
|
|
197
|
+
(!emptyAppRoot && !noscriptAsksForJavaScript)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const shell = emptyAppRoot
|
|
201
|
+
? `an empty app root (${emptyAppRoot})`
|
|
202
|
+
: "a <noscript> message asking for JavaScript";
|
|
203
|
+
return make(`The HTML has ${page.wordCount} words of text and ${shell}. GPTBot, OAI-SearchBot, ClaudeBot and PerplexityBot do not run JavaScript, so they see an empty page.`, {
|
|
204
|
+
selector: emptyAppRoot ?? "noscript",
|
|
205
|
+
evidence: {
|
|
206
|
+
wordCount: page.wordCount,
|
|
207
|
+
emptyAppRoot,
|
|
208
|
+
noscriptAsksForJavaScript,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}),
|
|
212
|
+
defineRule({
|
|
213
|
+
id: "aeo.question-not-answered-first",
|
|
214
|
+
category: "aeo",
|
|
215
|
+
severity: "info",
|
|
216
|
+
description: "A heading asks a question but no short, direct answer follows it.",
|
|
217
|
+
recommendation: "Answer each question in the first sentence or two under its heading (under about 60 words), then add detail. A direct answer right under the question is the passage answer engines find easiest to quote.",
|
|
218
|
+
}, ({ page }, make) => {
|
|
219
|
+
const weak = page.answer.questions.filter((q) => q.sectionWords === 0 ||
|
|
220
|
+
(q.firstBlockWords ?? q.sectionWords) > LONG_ANSWER_WORDS);
|
|
221
|
+
return weak.length
|
|
222
|
+
? make(`${weak.length} of ${page.answer.questions.length} question heading(s) are not followed by a short direct answer.`, {
|
|
223
|
+
evidence: {
|
|
224
|
+
questions: cap(weak.map((q) => ({
|
|
225
|
+
question: q.text,
|
|
226
|
+
firstBlockWords: q.firstBlockWords,
|
|
227
|
+
sectionWords: q.sectionWords,
|
|
228
|
+
}))),
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
: null;
|
|
232
|
+
}),
|
|
233
|
+
defineRule({
|
|
234
|
+
id: "aeo.unstructured-text",
|
|
235
|
+
category: "aeo",
|
|
236
|
+
severity: "info",
|
|
237
|
+
description: "A long page (600+ words) has no subheadings, lists or tables.",
|
|
238
|
+
recommendation: "Break the text into sections under descriptive subheadings, and use lists or tables for steps, options and comparisons, so answer engines can lift one self-contained passage.",
|
|
239
|
+
}, ({ page }, make) => page.wordCount >= 600 &&
|
|
240
|
+
!page.headings.some((h) => h.level >= 2) &&
|
|
241
|
+
page.answer.lists === 0 &&
|
|
242
|
+
page.answer.tables === 0
|
|
243
|
+
? make(`Page has ${page.wordCount} words without subheadings, lists or tables.`, { evidence: { wordCount: page.wordCount } })
|
|
244
|
+
: null),
|
|
245
|
+
defineRule({
|
|
246
|
+
id: "aeo.schema-text-not-visible",
|
|
247
|
+
category: "aeo",
|
|
248
|
+
severity: "warning",
|
|
249
|
+
description: "FAQPage, QAPage or HowTo structured data holds questions, answers or steps that are not in the page's HTML text.",
|
|
250
|
+
recommendation: "Mark up only what the page shows: render every question, answer and step from the same data as the JSON-LD, or remove the items that are not on the page. Google's structured data guidelines forbid marking up content readers cannot see. If the page adds them with JavaScript, render them on the server too: most AI crawlers do not run JavaScript.",
|
|
251
|
+
docs: AEO_DOCS.structuredDataPolicies,
|
|
252
|
+
}, ({ page }, make) => {
|
|
253
|
+
if (page.wordCount === 0) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const texts = markedUpTexts(page);
|
|
257
|
+
if (!texts.length) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const visible = ` ${comparable(page.text)} `;
|
|
261
|
+
// Compare the first words only: answers and steps are often longer, or formatted, on the page.
|
|
262
|
+
const hidden = texts.filter((t) => {
|
|
263
|
+
const words = comparable(stripTags(t.text))
|
|
264
|
+
.split(" ")
|
|
265
|
+
.slice(0, 12)
|
|
266
|
+
.join(" ");
|
|
267
|
+
return words && !visible.includes(` ${words} `);
|
|
268
|
+
});
|
|
269
|
+
return hidden.length
|
|
270
|
+
? make(`${hidden.length} of ${texts.length} marked-up question(s), answer(s) or step(s) do not appear in the page's HTML text.`, {
|
|
271
|
+
selector: 'script[type="application/ld+json"]',
|
|
272
|
+
evidence: {
|
|
273
|
+
missing: cap(hidden.map((t) => ({
|
|
274
|
+
type: t.type,
|
|
275
|
+
text: t.text.slice(0, 120),
|
|
276
|
+
}))),
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
: null;
|
|
280
|
+
}),
|
|
281
|
+
defineRule({
|
|
282
|
+
id: "aeo.article-author-missing",
|
|
283
|
+
category: "aeo",
|
|
284
|
+
severity: "warning",
|
|
285
|
+
description: "Article structured data names no author.",
|
|
286
|
+
recommendation: "Add author as a Person (or Organization) with name and url, the url pointing to an author page. Answer engines weigh who stands behind the content.",
|
|
287
|
+
docs: AEO_DOCS.article,
|
|
288
|
+
}, ({ page }, make) => {
|
|
289
|
+
const missing = articles(page).filter(({ node }) => {
|
|
290
|
+
const { author } = node;
|
|
291
|
+
return (author === undefined ||
|
|
292
|
+
author === null ||
|
|
293
|
+
(Array.isArray(author) && author.length === 0) ||
|
|
294
|
+
(isString(author) && !author.trim()));
|
|
295
|
+
});
|
|
296
|
+
return missing.length
|
|
297
|
+
? make(`${missing[0]?.types[0] ?? "Article"} structured data has no author.`, {
|
|
298
|
+
selector: 'script[type="application/ld+json"]',
|
|
299
|
+
evidence: { types: cap(missing.flatMap((m) => m.types)) },
|
|
300
|
+
})
|
|
301
|
+
: null;
|
|
302
|
+
}),
|
|
303
|
+
defineRule({
|
|
304
|
+
id: "aeo.article-author-incomplete",
|
|
305
|
+
category: "aeo",
|
|
306
|
+
severity: "info",
|
|
307
|
+
description: "The article's author is a bare name, without a url or sameAs that identifies the person.",
|
|
308
|
+
recommendation: 'Write the author as {"@type": "Person", "name": ..., "url": ...}, with url pointing to an author page, and sameAs listing the person\'s profiles where they exist.',
|
|
309
|
+
docs: AEO_DOCS.article,
|
|
310
|
+
}, ({ page }, make) => {
|
|
311
|
+
const bare = articles(page).flatMap(({ node }) => {
|
|
312
|
+
const authors = Array.isArray(node["author"])
|
|
313
|
+
? node["author"]
|
|
314
|
+
: [node["author"]];
|
|
315
|
+
return authors.flatMap((a) => {
|
|
316
|
+
if (isString(a) && a.trim()) {
|
|
317
|
+
return [a.trim()];
|
|
318
|
+
}
|
|
319
|
+
if (isJsonObject(a) &&
|
|
320
|
+
a["url"] === undefined &&
|
|
321
|
+
a["sameAs"] === undefined) {
|
|
322
|
+
return [nameOf(a) ?? "(unnamed author)"];
|
|
323
|
+
}
|
|
324
|
+
return [];
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
return bare.length
|
|
328
|
+
? make(`Article author ${bare.map((n) => `"${n}"`).join(", ")} has no url or sameAs.`, {
|
|
329
|
+
selector: 'script[type="application/ld+json"]',
|
|
330
|
+
evidence: { authors: cap(bare) },
|
|
331
|
+
})
|
|
332
|
+
: null;
|
|
333
|
+
}),
|
|
334
|
+
defineRule({
|
|
335
|
+
id: "aeo.article-dates-missing",
|
|
336
|
+
category: "aeo",
|
|
337
|
+
severity: "warning",
|
|
338
|
+
description: "Article structured data lacks datePublished (warning) or dateModified (info), which tell answer engines how fresh the content is.",
|
|
339
|
+
recommendation: "Add datePublished and dateModified in ISO 8601 (2026-03-14T09:30:00+01:00), and update dateModified when the content changes materially.",
|
|
340
|
+
docs: AEO_DOCS.article,
|
|
341
|
+
}, ({ page }, make) => {
|
|
342
|
+
const [article] = articles(page);
|
|
343
|
+
if (!article) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const missing = ["datePublished", "dateModified"].filter((p) => {
|
|
347
|
+
const value = article.node[p];
|
|
348
|
+
return !(isString(value) && value.trim());
|
|
349
|
+
});
|
|
350
|
+
if (!missing.length) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return make(`${article.types[0] ?? "Article"} structured data has no ${missing.join(" or ")}.`, {
|
|
354
|
+
severity: missing.includes("datePublished") ? "warning" : "info",
|
|
355
|
+
selector: 'script[type="application/ld+json"]',
|
|
356
|
+
evidence: { missing },
|
|
357
|
+
});
|
|
358
|
+
}),
|
|
359
|
+
defineRule({
|
|
360
|
+
id: "aeo.article-date-not-visible",
|
|
361
|
+
category: "aeo",
|
|
362
|
+
severity: "info",
|
|
363
|
+
description: "The article's structured data has a date, but the page shows none.",
|
|
364
|
+
recommendation: "Show the publish or update date near the title, matching the structured data; a <time datetime> element works well. Readers and answer engines use it to judge freshness.",
|
|
365
|
+
docs: AEO_DOCS.dates,
|
|
366
|
+
}, ({ page }, make) => {
|
|
367
|
+
const [article] = articles(page);
|
|
368
|
+
if (!article || page.wordCount === 0) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const years = [
|
|
372
|
+
yearOf(article.node["datePublished"]),
|
|
373
|
+
yearOf(article.node["dateModified"]),
|
|
374
|
+
].filter((y) => y !== null);
|
|
375
|
+
if (!years.length ||
|
|
376
|
+
page.answer.timeElements > 0 ||
|
|
377
|
+
page.answer.mentionsDate ||
|
|
378
|
+
years.some((y) => page.text.includes(y))) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
return make("Article has a date in its structured data, but none on the page.", {
|
|
382
|
+
evidence: {
|
|
383
|
+
datePublished: article.node["datePublished"] ?? null,
|
|
384
|
+
dateModified: article.node["dateModified"] ?? null,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
}),
|
|
388
|
+
defineRule({
|
|
389
|
+
id: "aeo.organization-missing",
|
|
390
|
+
category: "aeo",
|
|
391
|
+
severity: "info",
|
|
392
|
+
description: "The home page has no Organization (or Person) structured data.",
|
|
393
|
+
recommendation: "Add Organization JSON-LD to the home page (name, url, logo, and sameAs links to official profiles) so answer engines can tie the site to a known entity. Use Person for a personal site.",
|
|
394
|
+
docs: AEO_DOCS.organization,
|
|
395
|
+
}, ({ page, source }, make) => source !== "html" &&
|
|
396
|
+
isHomePage(page.url) &&
|
|
397
|
+
page.wordCount > 0 &&
|
|
398
|
+
!typedNodes(page.structuredData).some(isEntity)
|
|
399
|
+
? make("Home page has no Organization or Person structured data.")
|
|
400
|
+
: null),
|
|
401
|
+
defineRule({
|
|
402
|
+
id: "aeo.entity-same-as-missing",
|
|
403
|
+
category: "aeo",
|
|
404
|
+
severity: "info",
|
|
405
|
+
description: "Organization or Person structured data has no sameAs links to the entity's profiles elsewhere.",
|
|
406
|
+
recommendation: "Add sameAs with the URLs of the entity's official profiles (Wikipedia, Wikidata, LinkedIn, social accounts) so answer engines can match it to the entity they know.",
|
|
407
|
+
docs: AEO_DOCS.organization,
|
|
408
|
+
}, ({ page }, make) => {
|
|
409
|
+
const bare = typedNodes(page.structuredData).filter((n) => n.topLevel && isEntity(n) && n.node["sameAs"] === undefined);
|
|
410
|
+
return bare.length
|
|
411
|
+
? make(`${bare[0]?.types[0] ?? "Organization"} "${nameOf(bare[0]?.node["name"]) ?? "unnamed"}" has no sameAs links.`, {
|
|
412
|
+
selector: 'script[type="application/ld+json"]',
|
|
413
|
+
evidence: {
|
|
414
|
+
entities: cap(bare.map((n) => ({
|
|
415
|
+
type: n.types[0],
|
|
416
|
+
name: nameOf(n.node["name"]),
|
|
417
|
+
}))),
|
|
418
|
+
},
|
|
419
|
+
})
|
|
420
|
+
: null;
|
|
421
|
+
}),
|
|
422
|
+
];
|
|
423
|
+
//# sourceMappingURL=aeo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aeo.js","sourceRoot":"","sources":["../../../src/core/rules/aeo.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
+
import type { ResponseInfo } from "../types.js";
|
|
1
2
|
export declare const isLocalHost: (url: string) => boolean;
|
|
3
|
+
/** Targets of `Link: <url>; rel="canonical"` in the response headers, resolved against the page URL. */
|
|
4
|
+
export declare const headerCanonicals: (response: ResponseInfo | undefined, base: string) => string[];
|
|
2
5
|
export declare const canonicalRules: import("./rule.js").Rule[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { hostOf, normalizeUrl, samePath } from "../url.js";
|
|
1
|
+
import { hostOf, normalizeUrl, resolveUrl, samePath } from "../url.js";
|
|
2
2
|
import { cap, defineRule, GOOGLE_DOCS } from "./rule.js";
|
|
3
3
|
const SELECTOR = 'head > link[rel="canonical"]';
|
|
4
4
|
const LOCAL_HOSTS = new Set([
|
|
@@ -18,6 +18,34 @@ export const isLocalHost = (url) => {
|
|
|
18
18
|
host.endsWith(".localhost") ||
|
|
19
19
|
host.endsWith(".test"));
|
|
20
20
|
};
|
|
21
|
+
/*
|
|
22
|
+
* One `<url>; param; param` entry of a Link header. Neither character class crosses an angle
|
|
23
|
+
* bracket, so no match can restart inside another and matching stays linear on a long header.
|
|
24
|
+
*/
|
|
25
|
+
const LINK_ENTRY = /<(?<target>[^<>]*)>(?<params>[^<]*)/gu;
|
|
26
|
+
/** Targets of `Link: <url>; rel="canonical"` in the response headers, resolved against the page URL. */
|
|
27
|
+
export const headerCanonicals = (response, base) => {
|
|
28
|
+
const header = response?.headers["link"]?.slice(0, 16_384);
|
|
29
|
+
if (!header) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const match of header.matchAll(LINK_ENTRY)) {
|
|
34
|
+
const rel = match.groups?.params
|
|
35
|
+
?.split(";")
|
|
36
|
+
.map((p) => p.trim())
|
|
37
|
+
.find((p) => /^rel\s*=/iu.test(p));
|
|
38
|
+
const values = (rel?.slice(rel.indexOf("=") + 1) ?? "")
|
|
39
|
+
.replaceAll(/["']/gu, "")
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.split(/[\s,]+/u);
|
|
42
|
+
if (values.includes("canonical")) {
|
|
43
|
+
const target = match.groups?.target?.trim() ?? "";
|
|
44
|
+
out.push(resolveUrl(target, base) ?? target);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
};
|
|
21
49
|
const stripTrailingSlash = (url) => url.endsWith("/") && new URL(url).pathname !== "/" ? url.slice(0, -1) : url;
|
|
22
50
|
export const canonicalRules = [
|
|
23
51
|
defineRule({
|
|
@@ -27,9 +55,33 @@ export const canonicalRules = [
|
|
|
27
55
|
description: 'No <link rel="canonical"> is present.',
|
|
28
56
|
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.",
|
|
29
57
|
docs: GOOGLE_DOCS.canonical,
|
|
30
|
-
}, ({ page }, make) => page.canonicals.length === 0
|
|
58
|
+
}, ({ page, response }, make) => page.canonicals.length === 0 &&
|
|
59
|
+
headerCanonicals(response, page.url).length === 0
|
|
31
60
|
? make("Page has no canonical link.", { selector: SELECTOR })
|
|
32
61
|
: null),
|
|
62
|
+
defineRule({
|
|
63
|
+
id: "canonical.header-conflict",
|
|
64
|
+
category: "canonical",
|
|
65
|
+
severity: "error",
|
|
66
|
+
description: 'The HTTP Link header names a different canonical URL than the page\'s <link rel="canonical">, or names more than one.',
|
|
67
|
+
recommendation: 'Name one canonical URL, in one place: the <link rel="canonical"> in <head>, or the Link header (meant for PDFs and other files without HTML). Google asks not to give different canonical URLs through different methods. A CDN, plugin or platform rule often sets the header; remove the one you did not mean.',
|
|
68
|
+
docs: GOOGLE_DOCS.canonical,
|
|
69
|
+
}, ({ page, response }, make) => {
|
|
70
|
+
const header = headerCanonicals(response, page.url);
|
|
71
|
+
const targets = new Set(
|
|
72
|
+
// Google treats /page and /page/ as two URLs, so a trailing slash is a real difference.
|
|
73
|
+
[...header, ...(page.canonical ? [page.canonical] : [])].map((url) => normalizeUrl(url) ?? url));
|
|
74
|
+
return header.length > 0 && targets.size > 1
|
|
75
|
+
? make(page.canonical
|
|
76
|
+
? `The Link header names ${header.join(" and ")} as canonical, but <link rel="canonical"> names ${page.canonical}.`
|
|
77
|
+
: `The Link header names ${header.length} different canonical URLs.`, {
|
|
78
|
+
evidence: {
|
|
79
|
+
linkHeader: cap(header),
|
|
80
|
+
htmlCanonical: page.canonical,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
: null;
|
|
84
|
+
}),
|
|
33
85
|
defineRule({
|
|
34
86
|
id: "canonical.multiple",
|
|
35
87
|
category: "canonical",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/core/rules/canonical.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../src/core/rules/canonical.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,QAAQ,GAAG,8BAA8B,CAAC;AAEhD,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,WAAW;IACX,WAAW;IACX,SAAS;IACT,KAAK;IACL,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,GAAW,EAAW,EAAE;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAEzB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,CACL,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CACvB,CAAC;AACJ,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,GAAG,uCAAuC,CAAC;AAE3D,wGAAwG;AACxG,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,QAAkC,EAClC,IAAY,EACF,EAAE;IACZ,MAAM,MAAM,GAAG,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAE3D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,GAAa,EAAE,CAAC;IAEzB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM;YAC9B,EAAE,KAAK,CAAC,GAAG,CAAC;aACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAErC,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;aACpD,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC;aACxB,WAAW,EAAE;aACb,KAAK,CAAC,SAAS,CAAC,CAAC;QAEpB,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAClD,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAU,EAAE,CACjD,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAE9E,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,UAAU,CACR;QACE,EAAE,EAAE,mBAAmB;QACvB,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,uCAAuC;QACpD,cAAc,EACZ,mKAAmK;QACrK,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAC3B,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;QAC5B,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;QAC/C,CAAC,CAAC,IAAI,CAAC,6BAA6B,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QAC7D,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,2BAA2B;QAC/B,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,OAAO;QACjB,WAAW,EACT,uHAAuH;QACzH,cAAc,EACZ,kTAAkT;QACpT,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpD,MAAM,OAAO,GAAG,IAAI,GAAG;QACrB,wFAAwF;QACxF,CAAC,GAAG,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAC1D,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,GAAG,CAClC,CACF,CAAC;QAEF,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC;YAC1C,CAAC,CAAC,IAAI,CACF,IAAI,CAAC,SAAS;gBACZ,CAAC,CAAC,yBAAyB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,mDAAmD,IAAI,CAAC,SAAS,GAAG;gBACnH,CAAC,CAAC,yBAAyB,MAAM,CAAC,MAAM,4BAA4B,EACtE;gBACE,QAAQ,EAAE;oBACR,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC;oBACvB,aAAa,EAAE,IAAI,CAAC,SAAS;iBAC9B;aACF,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,OAAO;QACjB,WAAW,EACT,iGAAiG;QACnG,cAAc,EACZ,kGAAkG;QACpG,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAE/D,OAAO,IAAI,CACT,YAAY,IAAI,CAAC,UAAU,CAAC,MAAM,mBAAmB,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,cAAc,GAAG,EACtH;YACE,QAAQ,EAAE,QAAQ;YAClB,QAAQ,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;YACjD,QAAQ,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;SAC/C,CACF,CAAC;IACJ,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,mBAAmB;QACvB,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,OAAO;QACjB,WAAW,EACT,sEAAsE;QACxE,cAAc,EACZ,uFAAuF;QACzF,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;QAEhC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,SAAS;YACnB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,IAAI,CAAC,mBAAmB,KAAK,uBAAuB,EAAE;gBACpD,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;aAC1B,CAAC,CAAC;IACT,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,oBAAoB;QACxB,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,SAAS;QACnB,WAAW,EAAE,uCAAuC;QACpD,cAAc,EACZ,6HAA6H;QAC/H,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;QAEhC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;YACjC,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,IAAI,CAAC,mBAAmB,KAAK,gBAAgB,EAAE;gBAC7C,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE;aACpD,CAAC,CAAC;IACT,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,6BAA6B;QACjC,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,iEAAiE;QACnE,cAAc,EAAE,uCAAuC;QACvD,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE;QACzB,IAAI,MAAM,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACxC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;YACpC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC;YACpC,CAAC,CAAC,IAAI,CACF,gEAAgE,EAChE;gBACE,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;aACxD,CACF;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,sBAAsB;QAC1B,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,6GAA6G;QAC/G,cAAc,EACZ,0KAA0K;QAC5K,IAAI,EAAE,WAAW,CAAC,SAAS;KAC5B,EACD,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE;QACzB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE3C,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,6EAA6E;QAC7E,MAAM,QAAQ,GAAG,MAAM,KAAK,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE3D,MAAM,IAAI,GAAG,QAAQ;YACnB,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;YAC1B,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,KAAK,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAE9D,OAAO,IAAI;YACT,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,IAAI,CAAC,uBAAuB,IAAI,CAAC,SAAS,qBAAqB,EAAE;gBAC/D,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE;oBACR,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,IAAI,EAAE,IAAI,CAAC,GAAG;oBACd,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;iBACtC;aACF,CAAC,CAAC;IACT,CAAC,CACF;CACF,CAAC"}
|
|
@@ -54,9 +54,11 @@ export const documentRules = [
|
|
|
54
54
|
id: "charset.missing",
|
|
55
55
|
category: "document",
|
|
56
56
|
severity: "info",
|
|
57
|
-
description: "No character encoding is declared.",
|
|
57
|
+
description: "No character encoding is declared, in the HTML or in the Content-Type header.",
|
|
58
58
|
recommendation: 'Add <meta charset="utf-8"> as the first element in <head>.',
|
|
59
|
-
},
|
|
59
|
+
},
|
|
60
|
+
// The HTML spec asks for <meta charset> only when Content-Type carries no charset.
|
|
61
|
+
({ page, response }, make) => page.charset || /charset=/iu.test(response?.contentType ?? "")
|
|
60
62
|
? null
|
|
61
63
|
: make("No <meta charset> declaration found.", {
|
|
62
64
|
selector: "head > meta[charset]",
|
|
@@ -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,wCAAwC,CAAC;AAEvD,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,EACZ,wHAAwH;KAC3H,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,IAAI,CAAC,IAAI;QACP,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAClE;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,CACjB,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,CAAC,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,IAAI,iCAAiC,EAAE;YAC9D,QAAQ,EAAE,MAAM;YAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;SAC9B,CAAC;QACJ,CAAC,CAAC,IAAI,CACX;IACD,UAAU,CACR;QACE,EAAE,EAAE,kBAAkB;QACtB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,SAAS;QACnB,WAAW,EACT,2EAA2E;QAC7E,cAAc,EACZ,uIAAuI;QACzI,IAAI,EAAE,WAAW,CAAC,MAAM;KACzB,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CACjB,IAAI,CAAC,QAAQ;QACX,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,gCAAgC,EAAE;YACrC,QAAQ,EAAE,8BAA8B;SACzC,CAAC,CACT;IACD,UAAU,CACR;QACE,EAAE,EAAE,uBAAuB;QAC3B,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,MAAM;QAChB,WAAW,EACT,wEAAwE;QAC1E,cAAc,EACZ,oHAAoH;KACvH,EACD,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE;QACjB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAErE,OAAO,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC;YACtC,kCAAkC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1C,CAAC,CAAC,IAAI,CAAC,uCAAuC,EAAE;gBAC5C,QAAQ,EAAE,8BAA8B;gBACxC,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE;aACtC,CAAC;YACJ,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CACF;IACD,UAAU,CACR;QACE,EAAE,EAAE,iBAAiB;QACrB,QAAQ,EAAE,UAAU;QACpB,QAAQ,EAAE,MAAM;QAChB,WAAW,
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../../../src/core/rules/document.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const headRules: import("./rule.js").Rule[];
|