/** * ESLint rule to forbid raw HTML in app/ directory * * All HTML must be encapsulated in React components from components/ or ui/ * * Rationale: * - app/ should only contain page/layout components * - Raw HTML with styling violates separation of concerns * - UI logic belongs in components/ui layers */ module.exports = { meta: { type: 'problem', docs: { description: 'Forbid raw HTML with styling in app/ directory', category: 'Architecture', recommended: true, }, fixable: null, schema: [], messages: { noRawHtml: 'Raw HTML with styling is forbidden in app/. Use a component from components/ or ui/.', }, }, create(context) { const filename = context.getFilename(); const isInApp = filename.includes('/app/'); if (!isInApp) return {}; // HTML tags that should be wrapped in components const htmlTags = [ 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'button', 'input', 'form', 'label', 'select', 'textarea', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'thead', 'tbody', 'section', 'article', 'header', 'footer', 'nav', 'aside', 'main', 'aside', 'figure', 'figcaption', 'blockquote', 'code', 'pre', 'a', 'img', 'svg', 'path', 'g', 'rect', 'circle' ]; return { JSXElement(node) { const openingElement = node.openingElement; if (openingElement.name.type !== 'JSXIdentifier') return; const tagName = openingElement.name.name; // Check if it's a raw HTML element (lowercase) if (htmlTags.includes(tagName) && tagName[0] === tagName[0].toLowerCase()) { // Check for styling attributes const hasClassName = openingElement.attributes.some( attr => attr.type === 'JSXAttribute' && attr.name.name === 'className' ); const hasStyle = openingElement.attributes.some( attr => attr.type === 'JSXAttribute' && attr.name.name === 'style' ); // Check for inline event handlers (also a concern) const hasInlineHandlers = openingElement.attributes.some( attr => attr.type === 'JSXAttribute' && attr.name.name && attr.name.name.startsWith('on') ); if (hasClassName || hasStyle || hasInlineHandlers) { context.report({ node, messageId: 'noRawHtml', }); } } }, }; }, };