107 lines
3.6 KiB
JavaScript
107 lines
3.6 KiB
JavaScript
/**
|
|
* ESLint rule to enforce ViewModel and Builder architectural boundaries
|
|
*
|
|
* Rules:
|
|
* 1. ViewModels/Builders MUST NOT contain the word "DTO" in identifiers
|
|
* 2. ViewModels/Builders MUST NOT define inline DTO interfaces
|
|
* 3. ViewModels/Builders MUST NOT import from DTO paths (except generated types in Builders)
|
|
* 4. ViewModels MUST NOT define ViewData interfaces
|
|
*/
|
|
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description: 'Enforce ViewModel and Builder architectural boundaries',
|
|
category: 'Architecture',
|
|
recommended: true,
|
|
},
|
|
fixable: null,
|
|
schema: [],
|
|
messages: {
|
|
noDtoInViewModel: 'ViewModels and Builders must not use the word "DTO" in identifiers. DTOs belong to the API/Service layer. Use plain properties or ViewData types.',
|
|
noDtoImport: 'ViewModels must not import from DTO paths. DTOs belong to lib/types/generated/. Import from lib/view-data/ or use plain properties instead.',
|
|
noViewDataDefinition: 'ViewData must not be defined within ViewModel files. Import them from lib/view-data/ instead.',
|
|
noInlineDtoDefinition: 'DTOs must not be defined inline. Use generated types from lib/types/generated/ and import them.',
|
|
},
|
|
},
|
|
|
|
create(context) {
|
|
const filename = context.getFilename();
|
|
const isInViewModels = filename.includes('/lib/view-models/');
|
|
const isInBuilders = filename.includes('/lib/builders/');
|
|
|
|
if (!isInViewModels && !isInBuilders) return {};
|
|
|
|
return {
|
|
// Check for "DTO" in any identifier
|
|
Identifier(node) {
|
|
const name = node.name.toUpperCase();
|
|
if (name === 'DTO' || name.endsWith('DTO')) {
|
|
// Exception: Allow DTO in type references in Builders (for satisfies/input)
|
|
if (isInBuilders && (node.parent.type === 'TSTypeReference' || node.parent.type === 'TSQualifiedName')) {
|
|
return;
|
|
}
|
|
context.report({
|
|
node,
|
|
messageId: 'noDtoInViewModel',
|
|
});
|
|
}
|
|
},
|
|
|
|
// Check for imports from DTO paths
|
|
ImportDeclaration(node) {
|
|
const importPath = node.source.value;
|
|
|
|
// ViewModels are never allowed to import DTOs
|
|
if (isInViewModels && (
|
|
importPath.includes('/lib/types/generated/') ||
|
|
importPath.includes('/lib/dtos/') ||
|
|
importPath.includes('/lib/api/') ||
|
|
importPath.includes('/lib/services/')
|
|
)) {
|
|
context.report({
|
|
node,
|
|
messageId: 'noDtoImport',
|
|
});
|
|
}
|
|
},
|
|
|
|
// Check for ViewData definitions in ViewModels
|
|
TSInterfaceDeclaration(node) {
|
|
if (isInViewModels && node.id && node.id.name && node.id.name.endsWith('ViewData')) {
|
|
context.report({
|
|
node,
|
|
messageId: 'noViewDataDefinition',
|
|
});
|
|
}
|
|
|
|
// Check for inline DTO definitions in both ViewModels and Builders
|
|
if (node.id && node.id.name && node.id.name.toUpperCase().includes('DTO')) {
|
|
context.report({
|
|
node,
|
|
messageId: 'noInlineDtoDefinition',
|
|
});
|
|
}
|
|
},
|
|
|
|
TSTypeAliasDeclaration(node) {
|
|
if (isInViewModels && node.id && node.id.name && node.id.name.endsWith('ViewData')) {
|
|
context.report({
|
|
node,
|
|
messageId: 'noViewDataDefinition',
|
|
});
|
|
}
|
|
|
|
// Check for inline DTO definitions
|
|
if (node.id && node.id.name && node.id.name.toUpperCase().includes('DTO')) {
|
|
context.report({
|
|
node,
|
|
messageId: 'noInlineDtoDefinition',
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|