import { MIN_TOKEN_LENGTH, PATH_SEPARATOR, TOKEN_STOPS } from "#configuration/constants/leak.constants"; import { BACKTICK } from "#configuration/constants/inventory.constants"; import type { LeakMatch } from "#types/leak.types"; const IDENTIFIER_MARKS: ReadonlySet = new Set(["_", "-", ".", "@", PATH_SEPARATOR]); const inlineCodeSpans = function inlineCodeSpans(text: string): string[] { const spans: string[] = []; let open = text.indexOf(BACKTICK); while (open !== -1) { const close = text.indexOf(BACKTICK, open + 1); if (close === -1) { break; } const span = text.slice(open + 1, close).trim(); if (span.length > 0) { spans.push(span); } open = text.indexOf(BACKTICK, close + 1); } return spans; }; const plainTokens = function plainTokens(text: string): string[] { const tokens: string[] = []; let current = ""; for (const char of text) { if (TOKEN_STOPS.has(char)) { if (current.length >= MIN_TOKEN_LENGTH) { tokens.push(current); } current = ""; } else { current += char; } } if (current.length >= MIN_TOKEN_LENGTH) { tokens.push(current); } return tokens; }; const hasIdentifierShape = function hasIdentifierShape(token: string): boolean { for (const char of token) { if (IDENTIFIER_MARKS.has(char)) { return true; } } return false; }; export const identifierTokens = function identifierTokens(text: string): string[] { return plainTokens(text).filter(hasIdentifierShape); }; const isWorkspacePath = function isWorkspacePath(token: string, roots: ReadonlySet): boolean { const cut = token.indexOf(PATH_SEPARATOR); return cut > 0 && roots.has(token.slice(0, cut)); }; export const leaksIn = function leaksIn( text: string, tokens: ReadonlySet, roots: ReadonlySet, ): LeakMatch[] { const spanReason = function spanReason(span: string): LeakMatch["reason"] | null { if (tokens.has(span)) { return "inline-code"; } return isWorkspacePath(span, roots) ? "path" : null; }; const tokenReason = function tokenReason(token: string): LeakMatch["reason"] | null { if (isWorkspacePath(token, roots)) { return "path"; } return hasIdentifierShape(token) && tokens.has(token) ? "identifier" : null; }; const spans = inlineCodeSpans(text).flatMap((span) => { const reason = spanReason(span); return reason === null ? [] : [{ reason, token: span }]; }); const words = plainTokens(text).flatMap((token) => { const reason = tokenReason(token); return reason === null ? [] : [{ reason, token }]; }); const seen = new Set(); return [...spans, ...words].filter((match) => { const fresh = !seen.has(match.token); seen.add(match.token); return fresh; }); };