website refactor
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* ESLint rule to enforce Service function format
|
||||
*
|
||||
*
|
||||
* Services in lib/services/ must:
|
||||
* 1. Be classes named *Service (not functions)
|
||||
* 2. Not have side effects (no redirect, console.log, etc.)
|
||||
* 3. Use builders for data transformation
|
||||
* 2. Create their own dependencies (API Client, Logger, ErrorReporter)
|
||||
* 3. Return Result types
|
||||
* 4. NOT use redirect() or process.exit()
|
||||
* 5. CAN use console.error() for logging (allowed)
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
@@ -19,20 +21,22 @@ module.exports = {
|
||||
schema: [],
|
||||
messages: {
|
||||
notAClass: 'Services must be classes named *Service, not functions. Found function "{{name}}" in lib/services/',
|
||||
hasSideEffects: 'Services must be pure. Found side effect: {{effect}}',
|
||||
noRedirect: 'Services cannot use redirect(). Use PageQueries or Client Components for navigation.',
|
||||
noProcessExit: 'Services cannot use process.exit().',
|
||||
multipleExports: 'Service files should only export the Service class.',
|
||||
mustReturnResult: 'Service methods must return Result<T, DomainError>.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const filename = context.getFilename();
|
||||
const isInServices = filename.includes('/lib/services/');
|
||||
let hasSideEffect = false;
|
||||
let sideEffectType = '';
|
||||
let hasRedirect = false;
|
||||
let hasProcessExit = false;
|
||||
let hasMultipleExports = false;
|
||||
let hasFunctionExport = false;
|
||||
let functionName = '';
|
||||
let hasResultReturningMethod = false;
|
||||
|
||||
return {
|
||||
// Track function declarations
|
||||
@@ -59,30 +63,20 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
// Track redirect calls
|
||||
// Track redirect and process.exit calls
|
||||
CallExpression(node) {
|
||||
if (isInServices) {
|
||||
// Check for redirect()
|
||||
if (node.callee.type === 'Identifier' && node.callee.name === 'redirect') {
|
||||
hasSideEffect = true;
|
||||
sideEffectType = 'redirect()';
|
||||
hasRedirect = true;
|
||||
}
|
||||
|
||||
// Check for console.log, console.error, etc.
|
||||
if (node.callee.type === 'MemberExpression' &&
|
||||
node.callee.object.type === 'Identifier' &&
|
||||
node.callee.object.name === 'console') {
|
||||
hasSideEffect = true;
|
||||
sideEffectType = 'console.' + (node.callee.property.name || 'call');
|
||||
}
|
||||
|
||||
// Check for process.exit()
|
||||
if (node.callee.type === 'MemberExpression' &&
|
||||
node.callee.object.type === 'Identifier' &&
|
||||
node.callee.object.name === 'process' &&
|
||||
node.callee.property.name === 'exit') {
|
||||
hasSideEffect = true;
|
||||
sideEffectType = 'process.exit()';
|
||||
hasProcessExit = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -109,6 +103,30 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
// Check for Result-returning methods
|
||||
MethodDefinition(node) {
|
||||
if (isInServices && node.key.type === 'Identifier') {
|
||||
const returnType = node.value?.returnType;
|
||||
|
||||
if (returnType &&
|
||||
returnType.typeAnnotation &&
|
||||
returnType.typeAnnotation.typeName &&
|
||||
returnType.typeAnnotation.typeName.name === 'Promise' &&
|
||||
returnType.typeAnnotation.typeParameters &&
|
||||
returnType.typeAnnotation.typeParameters.params.length > 0) {
|
||||
|
||||
const resultType = returnType.typeAnnotation.typeParameters.params[0];
|
||||
|
||||
if (resultType.type === 'TSTypeReference' &&
|
||||
resultType.typeName &&
|
||||
resultType.typeName.type === 'Identifier' &&
|
||||
resultType.typeName.name === 'Result') {
|
||||
hasResultReturningMethod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'Program:exit'() {
|
||||
if (!isInServices) return;
|
||||
|
||||
@@ -121,12 +139,19 @@ module.exports = {
|
||||
});
|
||||
}
|
||||
|
||||
// Check for side effects
|
||||
if (hasSideEffect) {
|
||||
// Check for redirect
|
||||
if (hasRedirect) {
|
||||
context.report({
|
||||
node: context.getSourceCode().ast,
|
||||
messageId: sideEffectType === 'redirect()' ? 'noRedirect' : 'hasSideEffects',
|
||||
data: { effect: sideEffectType },
|
||||
messageId: 'noRedirect',
|
||||
});
|
||||
}
|
||||
|
||||
// Check for process.exit
|
||||
if (hasProcessExit) {
|
||||
context.report({
|
||||
node: context.getSourceCode().ast,
|
||||
messageId: 'noProcessExit',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -137,6 +162,10 @@ module.exports = {
|
||||
messageId: 'multipleExports',
|
||||
});
|
||||
}
|
||||
|
||||
// Check for Result-returning methods (warn if none found)
|
||||
// Note: This is a soft check - services might have private methods that don't return Result
|
||||
// The important thing is that the public API methods do
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user