/** * ESLint rules for Display Object Guardrails * * Enforces display object boundaries and purity */ module.exports = { // Rule 1: No IO in display objects 'no-io-in-display-objects': { meta: { type: 'problem', docs: { description: 'Forbid IO imports in display objects', category: 'Display Objects', }, messages: { message: 'DisplayObjects cannot import from api, services, page-queries, or view-models - see apps/website/lib/contracts/display-objects/DisplayObject.ts', }, }, create(context) { const forbiddenPaths = [ '@/lib/api/', '@/lib/services/', '@/lib/page-queries/', '@/lib/view-models/', '@/lib/presenters/', ]; return { ImportDeclaration(node) { const importPath = node.source.value; if (forbiddenPaths.some(path => importPath.includes(path)) && !isInComment(node)) { context.report({ node, messageId: 'message', }); } }, }; }, }, // Rule 2: No non-class display exports 'no-non-class-display-exports': { meta: { type: 'problem', docs: { description: 'Forbid non-class exports in display objects', category: 'Display Objects', }, messages: { message: 'Display Objects must be class-based and export only classes - see apps/website/lib/contracts/display-objects/DisplayObject.ts', }, }, create(context) { return { ExportNamedDeclaration(node) { if (node.declaration && (node.declaration.type === 'FunctionDeclaration' || (node.declaration.type === 'VariableDeclaration' && !node.declaration.declarations.some(d => d.init && d.init.type === 'ClassExpression')))) { context.report({ node, messageId: 'message', }); } }, ExportDefaultDeclaration(node) { if (node.declaration && node.declaration.type !== 'ClassDeclaration' && node.declaration.type !== 'ClassExpression') { context.report({ node, messageId: 'message', }); } }, }; }, }, }; // Helper functions function isInComment(node) { return false; }