99 lines
2.9 KiB
JavaScript
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;
|
|
} |