/** * ESLint rule to forbid instantiation in services * * Services should not instantiate other services, API clients, or adapters. * They should receive dependencies via constructor injection. */ module.exports = { meta: { type: 'problem', docs: { description: 'Forbid instantiation in services', category: 'Services', recommended: true, }, fixable: null, schema: [], messages: { noInstantiation: 'Services should not instantiate {{type}} "{{name}}". Use constructor injection instead.', noNewInServices: 'Services should not use new keyword. Use constructor injection.', }, }, create(context) { const filename = context.getFilename(); const isInServices = filename.includes('/lib/services/'); if (!isInServices) return {}; return { // Track new keyword usage NewExpression(node) { const callee = node.callee; // Get the name being instantiated let instantiatedName = ''; if (callee.type === 'Identifier') { instantiatedName = callee.name; } else if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') { instantiatedName = callee.property.name; } // Check if it's a service, API client, or adapter const isService = instantiatedName.endsWith('Service'); const isApiClient = instantiatedName.endsWith('ApiClient') || instantiatedName.endsWith('Client'); const isAdapter = instantiatedName.includes('Adapter'); const isRepository = instantiatedName.includes('Repository'); const isGateway = instantiatedName.includes('Gateway'); if (isService || isApiClient || isAdapter || isRepository || isGateway) { const type = isService ? 'Service' : isApiClient ? 'API Client' : isAdapter ? 'Adapter' : isRepository ? 'Repository' : 'Gateway'; context.report({ node, messageId: 'noInstantiation', data: { type, name: instantiatedName }, }); } else { // Any new keyword in services is suspicious context.report({ node, messageId: 'noNewInServices', }); } }, // Also check for object literal instantiation ObjectExpression(node) { // Check if this is being assigned to a variable that looks like a service const parent = node.parent; if (parent && parent.type === 'VariableDeclarator') { const varName = parent.id.name; if (varName.endsWith('Service') || varName.endsWith('Client') || varName.includes('Adapter')) { context.report({ node, messageId: 'noInstantiation', data: { type: 'object literal', name: varName }, }); } } }, }; }, };