/** * ESLint Rule: Page Query Must Use Builder * * Ensures page queries use builders to map their results */ module.exports = { meta: { type: 'problem', docs: { description: 'Ensure page queries use builders to map their results', category: 'Page Query', recommended: true, }, messages: { mustUseBuilder: 'Page queries must use Builders to map Page DTO to View Data/View Model. See apps/website/docs/architecture/write/BUILDERS.md', }, schema: [], }, create(context) { const filename = context.getFilename(); // Only apply to page query files if (!filename.includes('/lib/page-queries/') || !filename.endsWith('.ts')) { return {}; } let hasBuilderImport = false; let hasReturnStatement = false; return { // Check imports for builder ImportDeclaration(node) { const importPath = node.source.value; if (importPath.includes('/lib/builders/')) { hasBuilderImport = true; } }, // Check for return statements ReturnStatement(node) { hasReturnStatement = true; }, 'Program:exit'() { // Skip if file doesn't look like a page query const isPageQueryFile = filename.includes('/lib/page-queries/') && filename.endsWith('.ts') && !filename.endsWith('.test.ts') && !filename.includes('/result/'); if (!isPageQueryFile) return; // Check if it's a class-based page query const sourceCode = context.getSourceCode(); const classNode = sourceCode.ast.body.find(node => node.type === 'ClassDeclaration' && node.id && node.id.name.endsWith('PageQuery') ); if (!classNode) return; // Check if the class has an execute method const executeMethod = classNode.body.body.find(member => member.type === 'MethodDefinition' && member.key.type === 'Identifier' && member.key.name === 'execute' ); if (!executeMethod) return; // Check if the execute method uses a builder // Look for builder usage in the method body const methodBody = executeMethod.value.body; if (!methodBody) return; // Check if there's a builder import if (!hasBuilderImport) { context.report({ node: classNode, messageId: 'mustUseBuilder', }); } }, }; }, };