Files
gridpilot.gg/apps/website/eslint-rules/write-boundary-rules.js
2026-01-12 14:52:04 +01:00

99 lines
2.9 KiB
JavaScript

/**
* ESLint rules for Write Boundary Guardrails
*
* Enforces write operation boundaries
*/
module.exports = {
// Rule 1: No direct mutations in write boundaries
'no-direct-mutations-in-write-boundaries': {
meta: {
type: 'problem',
docs: {
description: 'Forbid direct mutations in write boundaries',
category: 'Write Boundary',
},
messages: {
message: 'Write boundaries must use mutation functions, not direct mutations - see apps/website/lib/contracts/write-boundaries/WriteBoundary.ts',
},
},
create(context) {
return {
AssignmentExpression(node) {
const filename = context.getFilename();
if (filename.includes('/lib/write-boundaries/')) {
// Check for direct property assignment
if (node.left.type === 'MemberExpression' &&
!isInMutationFunction(node)) {
context.report({
node,
messageId: 'message',
});
}
}
},
UpdateExpression(node) {
const filename = context.getFilename();
if (filename.includes('/lib/write-boundaries/') &&
!isInMutationFunction(node)) {
context.report({
node,
messageId: 'message',
});
}
},
};
},
},
// Rule 2: Write boundaries must use repository pattern
'write-boundaries-must-use-repository': {
meta: {
type: 'problem',
docs: {
description: 'Enforce repository pattern in write boundaries',
category: 'Write Boundary',
},
messages: {
message: 'Write boundaries must use repository pattern for data access - see apps/website/lib/contracts/write-boundaries/WriteBoundary.ts',
},
},
create(context) {
return {
CallExpression(node) {
const filename = context.getFilename();
if (filename.includes('/lib/write-boundaries/')) {
// Check for direct database access
if (node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
['db', 'prisma', 'knex'].includes(node.callee.object.name) &&
!isInComment(node)) {
context.report({
node,
messageId: 'message',
});
}
}
},
};
},
},
};
// Helper functions
function isInMutationFunction(node) {
// Check if node is inside a mutation function
let current = node;
while (current) {
if (current.type === 'FunctionDeclaration' || current.type === 'FunctionExpression') {
const name = current.id?.name || '';
return name.includes('mutate') || name.includes('update') || name.includes('create');
}
current = current.parent;
}
return false;
}
function isInComment(node) {
return false;
}