Files
gridpilot.gg/apps/website/eslint-rules/template-no-unsafe-html.js
2026-01-14 23:46:04 +01:00

81 lines
2.3 KiB
JavaScript

/**
* 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',
});
}
},
};
},
};