core/validators/site.validator.ts
core/validators/site.validator.ts is a file in Bane's Lab Build Scripts. 125 lines of code and 22 definitions.
import {
EMPTY_MAIN,
EXCLUDED_ROUTE,
INDEXED_ERROR_PAGE,
WEAK_DESCRIPTION,
malformedSearchKey,
missingText,
strayRoute,
unlistedRoute,
unservedLink,
weakTitle,
wrongAlternate,
wrongCanonical,
} from "#configuration/strings/site.strings";
import type { Finding, PageArtifact, Seen, Twins } from "#types/validation.types";
import { JSON_ROUTE, SEARCH_KEY_CHARACTERS, SEARCH_KEY_LENGTH } from "#configuration/constants/site.constants";
import { isFileRoute } from "#core/predicates/route.predicate";
import { locationsOf } from "#core/converters/build.converter";
const NOINDEX = "noindex";
const FRAGMENT = "#";
const QUERY = "?";
const SLASH = "/";
const isKeyText = function isKeyText(text: string): boolean {
for (let at = 0; at < text.length; at += 1) {
if (!SEARCH_KEY_CHARACTERS.has(text.charAt(at))) {
return false;
}
}
return text.length >= SEARCH_KEY_LENGTH.min && text.length <= SEARCH_KEY_LENGTH.max;
};
export const checkSearchKey = function checkSearchKey(file: string, text: string): Finding[] {
return isKeyText(text)
? []
: [{ file, message: malformedSearchKey(text.length, SEARCH_KEY_LENGTH.min, SEARCH_KEY_LENGTH.max) }];
};
export const checkAlternates = function checkAlternates(
file: string,
artifact: PageArtifact,
expected: Twins,
): Finding[] {
const pairs: readonly (readonly [string, string | null, string | null])[] = [
["Markdown", artifact.alternates.markdown, expected.markdown],
["JSON", artifact.alternates.json, expected.json],
];
return pairs
.filter(([, found, wanted]) => found !== wanted)
.map(([form, found, wanted]) => ({ file, message: wrongAlternate(form, String(found), String(wanted)) }));
};
export const checkIndexable = function checkIndexable(
file: string,
artifact: PageArtifact,
indexable: boolean,
): Finding[] {
const excluded = artifact.robots?.includes(NOINDEX) ?? false;
if (indexable === !excluded) {
return [];
}
return [{ file, message: indexable ? EXCLUDED_ROUTE : INDEXED_ERROR_PAGE }];
};
export const checkPage = function checkPage(
file: string,
artifact: PageArtifact,
address: string,
seen: Seen,
): Finding[] {
const findings: Finding[] = [];
if (artifact.text.length === 0) {
findings.push({ file, message: EMPTY_MAIN });
}
if (artifact.title.length === 0 || seen.titles.has(artifact.title)) {
findings.push({ file, message: weakTitle(artifact.title) });
}
if (
artifact.description === null ||
artifact.description.length === 0 ||
seen.descriptions.has(artifact.description)
) {
findings.push({ file, message: WEAK_DESCRIPTION });
}
if (artifact.canonical !== address) {
findings.push({ file, message: wrongCanonical(String(artifact.canonical), address) });
}
seen.titles.add(artifact.title);
if (artifact.description !== null) {
seen.descriptions.add(artifact.description);
}
return findings;
};
export const checkSitemap = function checkSitemap(file: string, xml: string, addresses: readonly string[]): Finding[] {
const listed = new Set(locationsOf(xml));
const registered = new Set(addresses);
return [
...addresses
.filter((address) => !listed.has(address))
.map((address) => ({ file, message: unlistedRoute(address) })),
...[...listed]
.filter((location) => !registered.has(location))
.map((location) => ({ file, message: strayRoute(location) })),
];
};
export const checkText = function checkText(file: string, text: string, required: readonly string[]): Finding[] {
return required
.filter((needle) => !text.includes(needle))
.map((needle) => ({ file, message: missingText(needle) }));
};
const routeOf = function routeOf(href: string): string {
const stops = [href.indexOf(QUERY), href.indexOf(FRAGMENT)].filter((at) => at !== -1);
return stops.length === 0 ? href : href.slice(0, Math.min(...stops));
};
const isPageLink = function isPageLink(href: string): boolean {
return href.startsWith(SLASH) && !isFileRoute(href) && !href.startsWith(JSON_ROUTE);
};
export const checkLinks = function checkLinks(
file: string,
artifact: PageArtifact,
served: ReadonlySet<string>,
): Finding[] {
return artifact.links
.filter((href) => isPageLink(href))
.filter((href) => {
const route = routeOf(href);
return route.length > 0 && !served.has(route);
})
.map((href) => ({ file, message: unservedLink(href) }));
};