import { CITE_CLOSE, CITE_OPEN, LINK_OPEN, TAG_SEPARATOR, TAG_WORD_LIMIT, } from "#configuration/constants/panel.constants"; import { PANEL_CITE_UNKNOWN, PANEL_TAG_SEPARATED, PANEL_TAG_TOO_LONG, PANEL_TAG_TWICE, PANEL_UNCITED, PANEL_UNTAGGED, } from "#configuration/strings/panel.strings"; import type { PanelSection, PayloadPanel } from "#types/panel.types"; import type { GraphFinding } from "#types/coverage.types"; import { WORD_SEPARATOR } from "#configuration/constants/leak.constants"; const wordCount = function wordCount(tag: string): number { return tag.split(WORD_SEPARATOR).filter((word) => word.length > 0).length; }; const tagFindings = function tagFindings(section: PanelSection, panel: PayloadPanel): string[] { if (panel.tag === null) { return [PANEL_UNTAGGED]; } const messages: string[] = []; if (wordCount(panel.tag) > TAG_WORD_LIMIT) { messages.push(PANEL_TAG_TOO_LONG + panel.tag); } if (panel.tag.includes(TAG_SEPARATOR)) { messages.push(PANEL_TAG_SEPARATED + panel.tag); } if (!section.prose.includes(CITE_OPEN + panel.tag + CITE_CLOSE)) { messages.push(PANEL_UNCITED + panel.tag); } return messages; }; const duplicateTags = function duplicateTags(section: PanelSection): string[] { const seen = new Set(); const twice: string[] = []; for (const panel of section.panels) { if (panel.tag !== null && seen.has(panel.tag)) { twice.push(panel.tag); } if (panel.tag !== null) { seen.add(panel.tag); } } return twice; }; const citesIn = function citesIn(prose: string): readonly string[] { const cites: string[] = []; let open = prose.indexOf(CITE_OPEN); while (open !== -1) { const close = prose.indexOf(CITE_CLOSE, open + CITE_OPEN.length); if (close === -1) { break; } if (prose.charAt(close + CITE_CLOSE.length) !== LINK_OPEN) { cites.push(prose.slice(open + CITE_OPEN.length, close)); } open = prose.indexOf(CITE_OPEN, close + CITE_CLOSE.length); } return cites; }; const unknownCites = function unknownCites(section: PanelSection): readonly string[] { const tags = new Set(section.panels.flatMap((panel) => (panel.tag === null ? [] : [panel.tag]))); return [...new Set(citesIn(section.prose))].filter((cite) => !tags.has(cite)); }; export const validatePanels = function validatePanels(page: string, sections: readonly PanelSection[]): GraphFinding[] { return sections.flatMap((section) => [ ...section.panels.flatMap((panel) => tagFindings(section, panel).map((message) => ({ message, page, section: section.id })), ), ...duplicateTags(section).map((tag) => ({ message: PANEL_TAG_TWICE + tag, page, section: section.id })), ...unknownCites(section).map((cite) => ({ message: PANEL_CITE_UNKNOWN + cite, page, section: section.id })), ]); }; export const panelCount = function panelCount(sections: readonly PanelSection[]): number { return sections.reduce((sum, section) => sum + section.panels.length, 0); };