/** * ESLint rules for Page Query Guardrails * * Enforces page query contracts and boundaries */ module.exports = { // Rule 1: No null returns in page queries 'no-null-returns-in-page-queries': { meta: { type: 'problem', docs: { description: 'Forbid null returns in page queries', category: 'Page Query', }, messages: { message: 'PageQueries must return PageQueryResult union, not null - see apps/website/lib/contracts/page-queries/PageQuery.ts', }, }, create(context) { return { ReturnStatement(node) { if (node.argument && node.argument.type === 'Literal' && node.argument.value === null && !isInComment(node)) { context.report({ node, messageId: 'message', }); } }, }; }, }, // Rule 2: Invalid page query filename 'invalid-page-query-filename': { meta: { type: 'problem', docs: { description: 'Enforce correct page query filename', category: 'Page Query', }, messages: { message: 'PageQuery files must end with PageQuery.ts - see apps/website/lib/contracts/page-queries/PageQuery.ts', }, }, create(context) { const filename = context.getFilename(); if (filename.includes('/page-queries/') && !filename.endsWith('PageQuery.ts')) { context.report({ loc: { line: 1, column: 0 }, messageId: 'message', }); } return {}; }, }, // Rule 3: PageQuery must implement contract 'pagequery-must-implement-contract': { meta: { type: 'problem', docs: { description: 'Enforce PageQuery interface implementation', category: 'Page Query', }, messages: { message: 'PageQuery class must implement PageQuery interface - see apps/website/lib/contracts/page-queries/PageQuery.ts', }, }, create(context) { return { ClassDeclaration(node) { const className = node.id?.name; if (className && className.endsWith('PageQuery')) { const hasPageQueryImpl = node.implements && node.implements.some(impl => { // Handle different AST node types for generic interfaces if (impl.expression.type === 'TSExpressionWithTypeArguments') { return impl.expression.expression.name === 'PageQuery'; } if (impl.expression.type === 'Identifier') { return impl.expression.name === 'PageQuery'; } return false; }); if (!hasPageQueryImpl) { context.report({ node, messageId: 'message', }); } } }, }; }, }, // Rule 4: PageQuery must have execute method 'pagequery-must-have-execute': { meta: { type: 'problem', docs: { description: 'Enforce PageQuery execute method', category: 'Page Query', }, messages: { message: 'PageQuery class must have execute(params) method - see apps/website/lib/contracts/page-queries/PageQuery.ts', }, }, create(context) { return { ClassDeclaration(node) { const className = node.id?.name; if (className && className.endsWith('PageQuery')) { const hasExecute = node.body.body.some(member => member.type === 'MethodDefinition' && member.key.type === 'Identifier' && member.key.name === 'execute' ); if (!hasExecute) { context.report({ node, messageId: 'message', }); } } }, }; }, }, // Rule 5: PageQuery execute return type 'pagequery-execute-return-type': { meta: { type: 'problem', docs: { description: 'Enforce PageQuery execute return type', category: 'Page Query', }, messages: { message: 'PageQuery execute() must return Promise> - see apps/website/lib/contracts/page-queries/PageQuery.ts', }, }, create(context) { return { MethodDefinition(node) { if (node.key.type === 'Identifier' && node.key.name === 'execute' && node.value.type === 'FunctionExpression') { const returnType = node.value.returnType; if (!returnType || !returnType.typeAnnotation || !returnType.typeAnnotation.typeName || returnType.typeAnnotation.typeName.name !== 'Promise') { context.report({ node, messageId: 'message', }); } } }, }; }, }, }; // Helper functions function isInComment(node) { return false; }