export function validateContent()

in src/validators.ts [22:76]


export function validateContent(content: string, url: string) {
	function throwError(suspiciousTag: string) {
		const message =
			`Validation failure: suspicious HTML tags found in ${url}:` +
			`\n   ${suspiciousTag}\n` +
			`Did you mistype a tag name or forget to match opening and closing tags?\n` +
			`If it's not a tag name, please use '&lt;' instead of '<'.`;
		throw new Error(message);
	}

	const withoutCodeBlocks = content
		.replace(/````[\s\S]*?````/g, "")
		.replace(/```[\s\S]*?```/g, "")
		.replace(/`[\s\S]*?`/g, "");

	const htmlTagRegex =
		/<\/([a-zA-Z0-9]+)>|<([a-zA-Z0-9]+)(?:\s+[^>]*)?([\/\\])>|<([a-zA-Z0-9]+)(?:\s+[^>]*)?[^\/]?>/g;

	let match;
	const tags: [string, string][] = [];

	while ((match = htmlTagRegex.exec(withoutCodeBlocks)) !== null) {
		const closingTag = match[1]; // Captures closing tags, e.g., `/div` in `</div>`
		const openingTag = match[2] ? match[2] : match[4]; // Captures opening tags, e.g., `div` in `<div>`
		if (
			match[3] === "\\" || // Closing bracket is ignored like in `<project\>`
			openingTag === "source" ||
			openingTag === "img" ||
			openingTag === "br" ||
			openingTag === "hr"
		) {
			continue;
		}
		const isSelfClosing = match[3] === "/"; // Detects self-closing tags, e.g., `/` in `<img />`

		// If it's a closing tag, ensure it matches the last opened tag
		if (closingTag) {
			const [lastOpeningTag, fullMatch] = tags.pop() || ["", ""];
			if (lastOpeningTag !== closingTag) {
				// Tag mismatch
				throwError(fullMatch + "[...]" + match[0]);
			}
		} else if (openingTag) {
			if (!isSelfClosing) {
				// Push opening tags (non-self-closing) onto the stack
				tags.push([openingTag, match[0]]);
			}
		}
	}

	// If the stack is not empty, there are unclosed tags
	if (tags.length !== 0) {
		throwError(tags[0][1]);
	}
}