UNPKG

@intlayer/core

Version:

Includes core Intlayer functions like translation, dictionary, and utility functions shared across multiple packages.

63 lines (62 loc) 1.76 kB
//#region src/transpiler/html/validateHTML.ts const VOID_HTML_ELEMENTS = new Set([ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr" ]); const TAG_REGEX = /<(\/)?([a-zA-Z][a-zA-Z0-9.-]*)\s*((?:[^\n]|\n(?!\n))*?)(\/?)>/g; /** * Validates that HTML tags in `content` are properly opened, nested, and closed. * Returns structured issues instead of logging to the console. * * False-positive exclusions: * - Tags whose attribute string starts with `://` are URL autolinks (e.g. `<https://…>`). */ const validateHTML = (content) => { const issues = []; const stack = []; for (const match of content.matchAll(TAG_REGEX)) { const isClosing = !!match[1]; const tagName = match[2]; const attrs = match[3]; const isSelfClosing = !!match[4]; if (attrs.trimStart().startsWith("://") || attrs.trimStart().startsWith(":")) continue; if (isClosing) if (stack.length === 0) issues.push({ type: "error", message: `Closing tag </${tagName}> has no matching opening tag` }); else { const last = stack[stack.length - 1]; if (last.tag.toLowerCase() !== tagName.toLowerCase()) issues.push({ type: "error", message: `Mismatched closing tag: expected </${last.tag}> but found </${tagName}>` }); stack.pop(); } else { const isVoidElement = VOID_HTML_ELEMENTS.has(tagName.toLowerCase()); if (!isSelfClosing && !isVoidElement) stack.push({ tag: tagName }); } } for (const unclosed of stack) issues.push({ type: "error", message: `Unclosed HTML tag: <${unclosed.tag}>` }); return { valid: issues.filter((i) => i.type === "error").length === 0, issues }; }; //#endregion export { VOID_HTML_ELEMENTS, validateHTML }; //# sourceMappingURL=validateHTML.mjs.map