Files
gridpilot.gg/apps/website/eslint-rules/services-rules.js
2026-01-14 02:02:24 +01:00

121 lines
3.5 KiB
JavaScript

/**
* ESLint rules for Services Guardrails
*
* Enforces service contracts and boundaries
*/
module.exports = {
// Rule 1: No external API calls in services
'no-external-api-in-services': {
meta: {
type: 'problem',
docs: {
description: 'Forbid external API calls in services',
category: 'Services',
},
messages: {
message: 'External API calls must be in adapters, not services - see apps/website/lib/contracts/services/Service.ts',
},
},
create(context) {
return {
CallExpression(node) {
const filename = context.getFilename();
if (filename.includes('/lib/services/')) {
// Check for fetch, axios, or other HTTP calls
if (node.callee.type === 'Identifier' &&
['fetch', 'axios'].includes(node.callee.name) &&
!isInComment(node)) {
context.report({
node,
messageId: 'message',
});
}
// Check for external API URLs
if (node.arguments.length > 0) {
const firstArg = node.arguments[0];
if (firstArg.type === 'Literal' &&
typeof firstArg.value === 'string' &&
(firstArg.value.startsWith('http') ||
firstArg.value.includes('api.') ||
firstArg.value.includes('.com'))) {
context.report({
node,
messageId: 'message',
});
}
}
}
},
};
},
},
// Rule 2: Services must be pure functions
'services-must-be-pure': {
meta: {
type: 'problem',
docs: {
description: 'Enforce service purity',
category: 'Services',
},
messages: {
message: 'Services must be pure functions, no side effects allowed - see apps/website/lib/contracts/services/Service.ts',
},
},
create(context) {
return {
CallExpression(node) {
const filename = context.getFilename();
if (filename.includes('/lib/services/')) {
// Check for common side effects
if (node.callee.type === 'MemberExpression') {
const object = node.callee.object;
const property = node.callee.property;
// DOM manipulation
if (object.type === 'Identifier' &&
['document', 'window'].includes(object.name)) {
context.report({
node,
messageId: 'message',
});
}
// State mutation
if (property.type === 'Identifier' &&
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].includes(property.name)) {
context.report({
node,
messageId: 'message',
});
}
}
// Direct assignment to external state
if (node.type === 'AssignmentExpression' &&
node.left.type === 'MemberExpression' &&
node.left.object.type === 'Identifier' &&
!isInFunctionScope(node)) {
context.report({
node,
messageId: 'message',
});
}
}
},
};
},
},
};
// Helper functions
function isInComment(node) {
return false;
}
function isInFunctionScope(node) {
// Simplified check
return false;
}