type ValidationResult = {
isValid: boolean;
errors: Array<{
message: string;
line: number;
column: number;
}>;
};
function validateHTML(html: string): ValidationResult {
const errors: ValidationResult['errors'] = [];
const lines = html.split('\n');
const stack: Array<{ tag: string; line: number; column: number }> = [];
const selfClosingTags = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
lines.forEach((line, lineIndex) => {
const tagRegex = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
let match;
while ((match = tagRegex.exec(line)) !== null) {
const [fullTag, tagName] = match;
const column = match.index;
const isClosingTag = fullTag.startsWith('</');
const isSelfClosing = selfClosingTags.has(tagName.toLowerCase()) || fullTag.endsWith('/>');
if (!isClosingTag && !isSelfClosing) {
stack.push({ tag: tagName.toLowerCase(), line: lineIndex + 1, column });
} else if (isClosingTag) {
if (stack.length === 0) {
errors.push({
message: `Unexpected closing tag: ${fullTag}`,
line: lineIndex + 1,
column
});
} else {
const lastOpenTag = stack.pop()!;
if (lastOpenTag.tag !== tagName.toLowerCase()) {
errors.push({
message: `Mismatched tags: expected </${lastOpenTag.tag}>, found ${fullTag}`,
line: lineIndex + 1,
column
});
}
}
}
}
});
stack.forEach(({ tag, line, column }) => {
errors.push({
message: `Unclosed tag: <${tag}>`,
line,
column
});
});
return { isValid: errors.length === 0, errors };
}
function validateCSS(css: string): ValidationResult {
const errors: ValidationResult['errors'] = [];
const lines = css.split('\n');
let inRule = false;
let openBraces = 0;
lines.forEach((line, lineIndex) => {
openBraces += (line.match(/{/g) || []).length;
openBraces -= (line.match(/}/g) || []).length;
if (openBraces < 0) {
errors.push({
message: 'Unexpected closing brace',
line: lineIndex + 1,
column: line.indexOf('}') + 1
});
}
if (inRule && !line.trim().endsWith('{') && !line.trim().endsWith('}') && !line.trim().endsWith(';')) {
errors.push({
message: 'Missing semicolon at end of declaration',
line: lineIndex + 1,
column: line.length
});
}
if (line.includes('{')) inRule = true;
if (line.includes('}')) inRule = false;
});
if (openBraces > 0) {
errors.push({
message: 'Unclosed rule (missing closing brace)',
line: lines.length,
column: lines[lines.length - 1].length
});
}