core/validators/element.validator.ts

core/validators/element.validator.ts is a file in Bane's Lab Build Scripts. 39 lines of code and 8 definitions.

import { ALT_ATTRIBUTE, ARIA_LABEL_ATTRIBUTE, HREF_ATTRIBUTE, TITLE_ATTRIBUTE } from "#core/ids/element.ids";
import { genericLink, namelessLink } from "#configuration/strings/element.strings";
import type { Finding } from "#types/validation.types";
import { isGenericLabel } from "@banes-lab/web/core/predicates/link.predicate.ts";
import { readDocument } from "#core/adapters/document.adapter";

const SPACE = " ";

const imageNames = function imageNames(anchor: Element): string {
    return [...anchor.querySelectorAll("img")]
        .map((image) => image.getAttribute(ALT_ATTRIBUTE)?.trim() ?? "")
        .filter((alt) => alt.length > 0)
        .join(SPACE);
};

const accessibleName = function accessibleName(anchor: Element): string {
    const candidates = [
        anchor.getAttribute(ARIA_LABEL_ATTRIBUTE) ?? "",
        anchor.textContent,
        imageNames(anchor),
        anchor.getAttribute(TITLE_ATTRIBUTE) ?? "",
    ];
    return candidates.map((candidate) => candidate.trim()).find((name) => name.length > 0) ?? "";
};

const findingOf = function findingOf(file: string, anchor: Element): Finding | null {
    const href = anchor.getAttribute(HREF_ATTRIBUTE) ?? "";
    const text = anchor.textContent.trim();
    if (accessibleName(anchor).length === 0) {
        return { file, message: namelessLink(href) };
    }
    if (isGenericLabel(text)) {
        return { file, message: genericLink(href, text) };
    }
    return null;
};

export const checkLinkNames = function checkLinkNames(file: string, html: string): Finding[] {
    return readDocument(html, (document) =>
        [...document.querySelectorAll("a[href]")]
            .map((anchor) => findingOf(file, anchor))
            .filter((finding): finding is Finding => finding !== null),
    );
};