/** * ESLint rule to forbid raw HTML and className in templates * * Templates must use proper reusable UI components instead of low level html and css. * To avoid workarounds using `Box` with tailwind classes, the `className` property is forbidden. */ module.exports = { meta: { type: 'problem', docs: { description: 'Forbid raw HTML and className in templates', category: 'Architecture', recommended: true, }, fixable: null, schema: [], messages: { noRawHtml: 'Raw HTML tags are forbidden in templates. Use UI components from ui/ instead.', noClassName: 'The className property is forbidden in templates. Use proper component props for styling.', noStyle: 'The style property is forbidden in templates. Use proper component props for styling.', }, }, create(context) { const filename = context.getFilename(); const isInTemplates = filename.includes('/templates/'); if (!isInTemplates) return {}; return { JSXOpeningElement(node) { let tagName = ''; if (node.name.type === 'JSXIdentifier') { tagName = node.name.name; } else if (node.name.type === 'JSXMemberExpression') { tagName = node.name.property.name; } if (!tagName) return; // 1. Forbid raw HTML tags (lowercase) if (tagName[0] === tagName[0].toLowerCase()) { context.report({ node, messageId: 'noRawHtml', }); } // 2. Forbid className property const classNameAttr = node.attributes.find( attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'className' ); if (classNameAttr) { context.report({ node: classNameAttr, messageId: 'noClassName', }); } // 3. Forbid style property const styleAttr = node.attributes.find( attr => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'style' ); if (styleAttr) { context.report({ node: styleAttr, messageId: 'noStyle', }); } }, }; }, };