@pseolint/core 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +27 -4
- package/dist/algorithms/content-effort/cache.d.ts +5 -0
- package/dist/algorithms/content-effort/cache.d.ts.map +1 -0
- package/dist/algorithms/content-effort/cache.js +23 -0
- package/dist/algorithms/content-effort/cache.js.map +1 -0
- package/dist/algorithms/content-effort/index.d.ts +4 -0
- package/dist/algorithms/content-effort/index.d.ts.map +1 -0
- package/dist/algorithms/content-effort/index.js +4 -0
- package/dist/algorithms/content-effort/index.js.map +1 -0
- package/dist/algorithms/content-effort/judge.d.ts +36 -0
- package/dist/algorithms/content-effort/judge.d.ts.map +1 -0
- package/dist/algorithms/content-effort/judge.js +69 -0
- package/dist/algorithms/content-effort/judge.js.map +1 -0
- package/dist/algorithms/content-effort/schema.d.ts +13 -0
- package/dist/algorithms/content-effort/schema.d.ts.map +1 -0
- package/dist/algorithms/content-effort/schema.js +20 -0
- package/dist/algorithms/content-effort/schema.js.map +1 -0
- package/dist/auditor.d.ts +18 -1
- package/dist/auditor.d.ts.map +1 -1
- package/dist/auditor.js +155 -16
- package/dist/auditor.js.map +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +18 -3
- package/dist/cache.js.map +1 -1
- package/dist/formatters/template-cards.js +32 -32
- package/dist/framework-detect.d.ts +6 -0
- package/dist/framework-detect.d.ts.map +1 -0
- package/dist/framework-detect.js +22 -0
- package/dist/framework-detect.js.map +1 -0
- package/dist/rule-references.d.ts.map +1 -1
- package/dist/rule-references.js +1 -0
- package/dist/rule-references.js.map +1 -1
- package/dist/rules/content/unique-value.d.ts +2 -2
- package/dist/rules/content/unique-value.d.ts.map +1 -1
- package/dist/rules/content/unique-value.js +8 -2
- package/dist/rules/content/unique-value.js.map +1 -1
- package/dist/rules/scope.d.ts.map +1 -1
- package/dist/rules/scope.js +1 -0
- package/dist/rules/scope.js.map +1 -1
- package/dist/rules/tech/csr-bailout.d.ts +8 -0
- package/dist/rules/tech/csr-bailout.d.ts.map +1 -0
- package/dist/rules/tech/csr-bailout.js +48 -0
- package/dist/rules/tech/csr-bailout.js.map +1 -0
- package/dist/rules/tech/soft-404.d.ts +6 -0
- package/dist/rules/tech/soft-404.d.ts.map +1 -1
- package/dist/rules/tech/soft-404.js +23 -0
- package/dist/rules/tech/soft-404.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/schemas/audit-summary.schema.json +300 -300
- package/dist/rules/aeo/non-replicable-value.d.ts +0 -9
- package/dist/rules/aeo/non-replicable-value.d.ts.map +0 -1
- package/dist/rules/aeo/non-replicable-value.js +0 -95
- package/dist/rules/aeo/non-replicable-value.js.map +0 -1
- package/dist/rules/cannibal/keyword-collision.d.ts +0 -3
- package/dist/rules/cannibal/keyword-collision.d.ts.map +0 -1
- package/dist/rules/cannibal/keyword-collision.js +0 -25
- package/dist/rules/cannibal/keyword-collision.js.map +0 -1
- package/dist/rules/cannibal/title-overlap.d.ts +0 -3
- package/dist/rules/cannibal/title-overlap.d.ts.map +0 -1
- package/dist/rules/cannibal/title-overlap.js +0 -43
- package/dist/rules/cannibal/title-overlap.js.map +0 -1
- package/dist/rules/content/heading-uniqueness.d.ts +0 -3
- package/dist/rules/content/heading-uniqueness.d.ts.map +0 -1
- package/dist/rules/content/heading-uniqueness.js +0 -56
- package/dist/rules/content/heading-uniqueness.js.map +0 -1
- package/dist/rules/links/hub-pages.d.ts +0 -7
- package/dist/rules/links/hub-pages.d.ts.map +0 -1
- package/dist/rules/links/hub-pages.js +0 -73
- package/dist/rules/links/hub-pages.js.map +0 -1
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { load } from "cheerio";
|
|
2
|
-
const DEFAULT_INTERACTIVE_SELECTORS = [
|
|
3
|
-
"form",
|
|
4
|
-
"input:not([type=hidden])",
|
|
5
|
-
"select",
|
|
6
|
-
"textarea",
|
|
7
|
-
"iframe",
|
|
8
|
-
"button[type=submit]",
|
|
9
|
-
"canvas",
|
|
10
|
-
"[data-interactive]",
|
|
11
|
-
"[data-calculator]",
|
|
12
|
-
"[data-tool]",
|
|
13
|
-
"[data-widget]",
|
|
14
|
-
"[x-data]",
|
|
15
|
-
".calculator",
|
|
16
|
-
".tool",
|
|
17
|
-
".checker",
|
|
18
|
-
".generator",
|
|
19
|
-
".interactive",
|
|
20
|
-
".widget",
|
|
21
|
-
".comparator",
|
|
22
|
-
];
|
|
23
|
-
const DOWNLOAD_PATTERN = /\.(pdf|docx?|xlsx?|csv|zip|pptx?)(?:$|[?#])/i;
|
|
24
|
-
const GATED_PATTERNS = [
|
|
25
|
-
/\bsign\s*(in|up)\s+to\s+(read|access|view|download|continue)/i,
|
|
26
|
-
/\bpaywall\b/i,
|
|
27
|
-
/\bsubscribe\s+to\s+read\b/i,
|
|
28
|
-
/\blog\s*in\s+to\s+(read|access|view|download|continue)/i,
|
|
29
|
-
];
|
|
30
|
-
function hasInteractiveElement($, html, extraSelectors) {
|
|
31
|
-
const selectors = [...DEFAULT_INTERACTIVE_SELECTORS, ...extraSelectors];
|
|
32
|
-
for (const sel of selectors) {
|
|
33
|
-
try {
|
|
34
|
-
if ($(sel).length > 0)
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// invalid selector — skip
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Framework-generated component markers use hashed attribute names; scan raw HTML for common patterns.
|
|
42
|
-
if (/\bdata-(reactroot|react-[\w-]+|vue-[\w-]+|v-[\w-]+|svelte-[\w-]+)\b/i.test(html))
|
|
43
|
-
return true;
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
function hasDownloadableAsset($) {
|
|
47
|
-
let found = false;
|
|
48
|
-
$("a[href]").each((_, el) => {
|
|
49
|
-
if (found)
|
|
50
|
-
return false;
|
|
51
|
-
const href = $(el).attr("href") ?? "";
|
|
52
|
-
if (DOWNLOAD_PATTERN.test(href) || /[?&]format=(pdf|docx?|xlsx?|csv)\b/i.test(href)) {
|
|
53
|
-
found = true;
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
if ($(el).attr("download") !== undefined) {
|
|
57
|
-
found = true;
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
return undefined;
|
|
61
|
-
});
|
|
62
|
-
return found;
|
|
63
|
-
}
|
|
64
|
-
function hasGatedContent(text) {
|
|
65
|
-
return GATED_PATTERNS.some((re) => re.test(text));
|
|
66
|
-
}
|
|
67
|
-
export function nonReplicableValueRule(pages, options) {
|
|
68
|
-
const extra = options?.interactiveSelectors ?? [];
|
|
69
|
-
const severity = options?.severity ?? "warning";
|
|
70
|
-
const findings = [];
|
|
71
|
-
for (const page of pages) {
|
|
72
|
-
const $ = load(page.html);
|
|
73
|
-
const interactive = hasInteractiveElement($, page.html, extra);
|
|
74
|
-
const downloadable = hasDownloadableAsset($);
|
|
75
|
-
const gated = hasGatedContent(page.contentText);
|
|
76
|
-
if (interactive || downloadable || gated)
|
|
77
|
-
continue;
|
|
78
|
-
findings.push({
|
|
79
|
-
ruleId: "aeo/non-replicable-value",
|
|
80
|
-
severity,
|
|
81
|
-
message: `${page.url} contains only text AI can fully summarize — no interactive element, downloadable asset, or gated content detected.`,
|
|
82
|
-
pageUrl: page.url,
|
|
83
|
-
fix: `Add a non-replicable value so users have a reason to click through instead of accepting the AI summary. Options: ` +
|
|
84
|
-
`(1) an interactive tool specific to this page's topic (calculator, checker, comparator, generator), ` +
|
|
85
|
-
`(2) an interactive checklist users can complete, ` +
|
|
86
|
-
`(3) a downloadable asset (PDF, spreadsheet, dataset) visible above the fold, ` +
|
|
87
|
-
`(4) a live preview / try-it-yourself widget, ` +
|
|
88
|
-
`(5) a gated resource (account required). ` +
|
|
89
|
-
`For single-page apps, ensure interactive elements are present in the server-rendered HTML ` +
|
|
90
|
-
`so crawlers see them — client-only widgets are invisible to this audit AND to most AI crawlers.`,
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
return findings;
|
|
94
|
-
}
|
|
95
|
-
//# sourceMappingURL=non-replicable-value.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"non-replicable-value.js","sourceRoot":"","sources":["../../../src/rules/aeo/non-replicable-value.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"keyword-collision.d.ts","sourceRoot":"","sources":["../../../src/rules/cannibal/keyword-collision.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7D,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,GAAE,MAAU,GACpB,UAAU,EAAE,CAwBd"}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { buildCorpus, extractKeywords } from "../../algorithms/tf-idf.js";
|
|
2
|
-
export function keywordCollisionRule(pages, minShared = 6) {
|
|
3
|
-
const findings = [];
|
|
4
|
-
const corpus = buildCorpus(pages.map((page) => page.contentText));
|
|
5
|
-
const keywordsPerPage = pages.map((page) => extractKeywords(page.contentText, corpus, 10));
|
|
6
|
-
for (let i = 0; i < pages.length; i += 1) {
|
|
7
|
-
for (let j = i + 1; j < pages.length; j += 1) {
|
|
8
|
-
const setA = new Set(keywordsPerPage[i]);
|
|
9
|
-
const shared = keywordsPerPage[j].filter((kw) => setA.has(kw));
|
|
10
|
-
if (shared.length >= minShared) {
|
|
11
|
-
findings.push({
|
|
12
|
-
ruleId: "cannibal/keyword-collision",
|
|
13
|
-
severity: "warning",
|
|
14
|
-
message: `${pages[i].url} and ${pages[j].url} share ${shared.length} of their top 10 keywords: ${shared.join(", ")}.`,
|
|
15
|
-
pageUrl: pages[i].url,
|
|
16
|
-
relatedUrls: [pages[j].url],
|
|
17
|
-
similarity: shared.length / 10,
|
|
18
|
-
fix: `These pages target the same keywords. Consolidate them into one page or differentiate their content focus.`
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return findings;
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=keyword-collision.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"keyword-collision.js","sourceRoot":"","sources":["../../../src/rules/cannibal/keyword-collision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAG1E,MAAM,UAAU,oBAAoB,CAClC,KAAmB,EACnB,YAAoB,CAAC;IAErB,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAClE,MAAM,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;IAE3F,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/D,IAAI,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;gBAC/B,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,4BAA4B;oBACpC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,MAAM,CAAC,MAAM,8BAA8B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;oBACrH,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG;oBACrB,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;oBAC3B,UAAU,EAAE,MAAM,CAAC,MAAM,GAAG,EAAE;oBAC9B,GAAG,EAAE,4GAA4G;iBAClH,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"title-overlap.d.ts","sourceRoot":"","sources":["../../../src/rules/cannibal/title-overlap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA0BhF,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,SAAS,EAAE,MAAM,GAChB,UAAU,EAAE,CAsBd"}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { maskEntities } from "../../algorithms/entity-mask.js";
|
|
2
|
-
function tokenize(text) {
|
|
3
|
-
return new Set(text
|
|
4
|
-
.toLowerCase()
|
|
5
|
-
.replace(/[^\p{L}\p{N}\s]+/gu, " ")
|
|
6
|
-
.split(/\s+/)
|
|
7
|
-
.filter(Boolean));
|
|
8
|
-
}
|
|
9
|
-
function jaccardSimilarity(a, b) {
|
|
10
|
-
if (a.size === 0 && b.size === 0) {
|
|
11
|
-
return 0;
|
|
12
|
-
}
|
|
13
|
-
let intersection = 0;
|
|
14
|
-
for (const token of a) {
|
|
15
|
-
if (b.has(token)) {
|
|
16
|
-
intersection += 1;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
const union = a.size + b.size - intersection;
|
|
20
|
-
return union === 0 ? 0 : intersection / union;
|
|
21
|
-
}
|
|
22
|
-
export function titleOverlapRule(pages, patterns, threshold) {
|
|
23
|
-
const findings = [];
|
|
24
|
-
const maskedTokens = pages.map((page) => tokenize(maskEntities(page.title, patterns)));
|
|
25
|
-
for (let i = 0; i < pages.length; i += 1) {
|
|
26
|
-
for (let j = i + 1; j < pages.length; j += 1) {
|
|
27
|
-
const similarity = jaccardSimilarity(maskedTokens[i], maskedTokens[j]);
|
|
28
|
-
if (similarity > threshold) {
|
|
29
|
-
findings.push({
|
|
30
|
-
ruleId: "cannibal/title-overlap",
|
|
31
|
-
severity: "warning",
|
|
32
|
-
message: `${pages[i].url} and ${pages[j].url} have overlapping titles after entity masking (${(similarity * 100).toFixed(1)}% Jaccard similarity).`,
|
|
33
|
-
pageUrl: pages[i].url,
|
|
34
|
-
relatedUrls: [pages[j].url],
|
|
35
|
-
similarity,
|
|
36
|
-
fix: `Differentiate page titles by including unique, page-specific keywords or angles.`
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return findings;
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=title-overlap.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"title-overlap.js","sourceRoot":"","sources":["../../../src/rules/cannibal/title-overlap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAG/D,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI,GAAG,CACZ,IAAI;SACD,WAAW,EAAE;SACb,OAAO,CAAC,oBAAoB,EAAE,GAAG,CAAC;SAClC,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,OAAO,CAAC,CACnB,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAc,EAAE,CAAc;IACvD,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACjB,YAAY,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,YAAY,CAAC;IAC7C,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,KAAmB,EACnB,QAA6B,EAC7B,SAAiB;IAEjB,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEvF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,UAAU,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;YACvE,IAAI,UAAU,GAAG,SAAS,EAAE,CAAC;gBAC3B,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,wBAAwB;oBAChC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,kDAAkD,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,wBAAwB;oBACnJ,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG;oBACrB,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;oBAC3B,UAAU;oBACV,GAAG,EAAE,kFAAkF;iBACxF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"heading-uniqueness.d.ts","sourceRoot":"","sources":["../../../src/rules/content/heading-uniqueness.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAMhF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,UAAU,EAAE,CAmDd"}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { maskEntities } from "../../algorithms/entity-mask.js";
|
|
2
|
-
function norm(value, patterns) {
|
|
3
|
-
return maskEntities(value.toLowerCase().trim(), patterns);
|
|
4
|
-
}
|
|
5
|
-
export function headingUniquenessRule(pages, patterns) {
|
|
6
|
-
const h1Groups = new Map();
|
|
7
|
-
const h2Groups = new Map();
|
|
8
|
-
for (const page of pages) {
|
|
9
|
-
const h1Key = page.headings.h1.map((h) => norm(h, patterns)).join(" | ");
|
|
10
|
-
const h2Key = page.headings.h2.map((h) => norm(h, patterns)).join(" | ");
|
|
11
|
-
if (h1Key) {
|
|
12
|
-
const group = h1Groups.get(h1Key) ?? [];
|
|
13
|
-
group.push(page.url);
|
|
14
|
-
h1Groups.set(h1Key, group);
|
|
15
|
-
}
|
|
16
|
-
if (h2Key) {
|
|
17
|
-
const group = h2Groups.get(h2Key) ?? [];
|
|
18
|
-
group.push(page.url);
|
|
19
|
-
h2Groups.set(h2Key, group);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const findings = [];
|
|
23
|
-
const emitted = new Set();
|
|
24
|
-
for (const [heading, urls] of h1Groups) {
|
|
25
|
-
if (urls.length < 2)
|
|
26
|
-
continue;
|
|
27
|
-
const key = `h1::${heading}`;
|
|
28
|
-
if (emitted.has(key))
|
|
29
|
-
continue;
|
|
30
|
-
emitted.add(key);
|
|
31
|
-
findings.push({
|
|
32
|
-
ruleId: "content/heading-uniqueness",
|
|
33
|
-
severity: "warning",
|
|
34
|
-
message: `${urls.length} pages share identical normalized H1 heading.`,
|
|
35
|
-
relatedUrls: urls.sort(),
|
|
36
|
-
fix: "Write unique headings for each page that reflect its specific content."
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
for (const [heading, urls] of h2Groups) {
|
|
40
|
-
if (urls.length < 2)
|
|
41
|
-
continue;
|
|
42
|
-
const key = `h2::${heading}`;
|
|
43
|
-
if (emitted.has(key))
|
|
44
|
-
continue;
|
|
45
|
-
emitted.add(key);
|
|
46
|
-
findings.push({
|
|
47
|
-
ruleId: "content/heading-uniqueness",
|
|
48
|
-
severity: "warning",
|
|
49
|
-
message: `${urls.length} pages share identical normalized H2 headings.`,
|
|
50
|
-
relatedUrls: urls.sort(),
|
|
51
|
-
fix: "Write unique headings for each page that reflect its specific content."
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return findings;
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=heading-uniqueness.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"heading-uniqueness.js","sourceRoot":"","sources":["../../../src/rules/content/heading-uniqueness.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ParsedPage, RuleResult } from "../../types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Hub/index coverage for medium-sized directories, plus info when a cluster is skipped
|
|
4
|
-
* because it exceeds `maxSiblings`.
|
|
5
|
-
*/
|
|
6
|
-
export declare function hubPagesRule(pages: ParsedPage[], knownUrls: Set<string>, minSiblings: number, maxSiblings: number): RuleResult[];
|
|
7
|
-
//# sourceMappingURL=hub-pages.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hub-pages.d.ts","sourceRoot":"","sources":["../../../src/rules/links/hub-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAmB7D;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,UAAU,EAAE,CA8Dd"}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { clusterKeyForUrl } from "./cluster-key.js";
|
|
2
|
-
const INDEX_NAMES = ["index.html", "index.htm"];
|
|
3
|
-
function indexUrlsForCluster(clusterDir, pageUrl) {
|
|
4
|
-
if (/^https?:\/\//i.test(pageUrl)) {
|
|
5
|
-
try {
|
|
6
|
-
const base = new URL(clusterDir);
|
|
7
|
-
return INDEX_NAMES.map((name) => new URL(name, base).href);
|
|
8
|
-
}
|
|
9
|
-
catch {
|
|
10
|
-
return [];
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
const sep = pageUrl.includes("\\") ? "\\" : "/";
|
|
14
|
-
const d = clusterDir.replace(/[/\\]+$/, "");
|
|
15
|
-
return INDEX_NAMES.map((n) => `${d}${sep}${n}`);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Hub/index coverage for medium-sized directories, plus info when a cluster is skipped
|
|
19
|
-
* because it exceeds `maxSiblings`.
|
|
20
|
-
*/
|
|
21
|
-
export function hubPagesRule(pages, knownUrls, minSiblings, maxSiblings) {
|
|
22
|
-
if (pages.length === 0) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
const byCluster = new Map();
|
|
26
|
-
for (const p of pages) {
|
|
27
|
-
const key = clusterKeyForUrl(p.url);
|
|
28
|
-
const list = byCluster.get(key) ?? [];
|
|
29
|
-
list.push(p);
|
|
30
|
-
byCluster.set(key, list);
|
|
31
|
-
}
|
|
32
|
-
const findings = [];
|
|
33
|
-
for (const [clusterDir, group] of byCluster.entries()) {
|
|
34
|
-
if (group.length < minSiblings) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
if (group.length > maxSiblings) {
|
|
38
|
-
findings.push({
|
|
39
|
-
ruleId: "links/hub-pages-skipped",
|
|
40
|
-
severity: "info",
|
|
41
|
-
message: `Hub/index check skipped for cluster ${clusterDir} (${group.length} pages > max ${maxSiblings}).`,
|
|
42
|
-
relatedUrls: group.map((p) => p.url).sort(),
|
|
43
|
-
fix: "Create an index or hub page for this directory that links to all child pages."
|
|
44
|
-
});
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
const siblingUrls = new Set(group.map((p) => p.url));
|
|
48
|
-
const indexCandidates = indexUrlsForCluster(clusterDir, group[0].url);
|
|
49
|
-
const hasIndex = indexCandidates.some((u) => knownUrls.has(u));
|
|
50
|
-
const linksToAllSiblings = (page) => {
|
|
51
|
-
const linked = new Set(page.resolvedHrefs.filter((u) => knownUrls.has(u) && siblingUrls.has(u)));
|
|
52
|
-
linked.add(page.url);
|
|
53
|
-
for (const s of siblingUrls) {
|
|
54
|
-
if (!linked.has(s)) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
59
|
-
};
|
|
60
|
-
const hasHub = hasIndex || group.some((p) => linksToAllSiblings(p));
|
|
61
|
-
if (!hasHub) {
|
|
62
|
-
findings.push({
|
|
63
|
-
ruleId: "links/hub-pages",
|
|
64
|
-
severity: "warning",
|
|
65
|
-
message: `No hub/index page detected for cluster ${clusterDir} (${group.length} pages).`,
|
|
66
|
-
relatedUrls: Array.from(siblingUrls).sort(),
|
|
67
|
-
fix: "Create an index or hub page for this directory that links to all child pages."
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return findings;
|
|
72
|
-
}
|
|
73
|
-
//# sourceMappingURL=hub-pages.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hub-pages.js","sourceRoot":"","sources":["../../../src/rules/links/hub-pages.ts"],"names":[],"mappings":"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"}
|