view data fixes
This commit is contained in:
@@ -41,8 +41,8 @@ module.exports = {
|
||||
const importPath = node.source.value;
|
||||
|
||||
// Check for DTO imports (should be from lib/types/generated/)
|
||||
if (importPath.includes('/lib/types/')) {
|
||||
if (!importPath.includes('/lib/types/generated/')) {
|
||||
if (importPath.includes('/lib/types/') || importPath.includes('@/lib/types/') || importPath.includes('../../types/')) {
|
||||
if (!importPath.includes('/lib/types/generated/') && !importPath.includes('@/lib/types/generated/') && !importPath.includes('../../types/generated/')) {
|
||||
dtoImportPath = importPath;
|
||||
context.report({
|
||||
node,
|
||||
@@ -55,7 +55,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Check for ViewData imports (should be from lib/view-data/)
|
||||
if (importPath.includes('/lib/view-data/')) {
|
||||
if (importPath.includes('/lib/view-data/') || importPath.includes('@/lib/view-data/') || importPath.includes('../../view-data/')) {
|
||||
hasViewDataImport = true;
|
||||
viewDataImportPath = importPath;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
/**
|
||||
* ESLint rule to enforce ViewModel architectural boundaries
|
||||
* ESLint rule to enforce ViewModel and Builder architectural boundaries
|
||||
*
|
||||
* ViewModels in lib/view-models/ must:
|
||||
* 1. NOT contain the word "DTO" (DTOs are for API/Services)
|
||||
* 2. NOT define ViewData interfaces (ViewData must be in lib/view-data/)
|
||||
* 3. NOT import from DTO paths (DTOs belong to lib/types/generated/)
|
||||
* 4. ONLY import from allowed paths: lib/contracts/, lib/view-models/, lib/view-data/, lib/formatters/
|
||||
* 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 architectural boundaries',
|
||||
description: 'Enforce ViewModel and Builder architectural boundaries',
|
||||
category: 'Architecture',
|
||||
recommended: true,
|
||||
},
|
||||
fixable: null,
|
||||
schema: [],
|
||||
messages: {
|
||||
noDtoInViewModel: 'ViewModels must not use the word "DTO". DTOs belong to the API/Service layer. Use plain logic-rich properties or ViewData types.',
|
||||
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.',
|
||||
strictImport: 'ViewModels can only import from lib/contracts/, lib/view-models/, lib/view-data/, or lib/formatters/. External imports are allowed. Found: {{importPath}}',
|
||||
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) return {};
|
||||
if (!isInViewModels && !isInBuilders) return {};
|
||||
|
||||
return {
|
||||
// Check for "DTO" in any identifier (variable, class, interface, property)
|
||||
// Only catch identifiers that end with "DTO" or are exactly "DTO"
|
||||
// This avoids false positives like "formattedTotalSpent" which contains "DTO" as a substring
|
||||
// Check for "DTO" in any identifier
|
||||
Identifier(node) {
|
||||
const name = node.name.toUpperCase();
|
||||
// Only catch identifiers that end with "DTO" or are exactly "DTO"
|
||||
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',
|
||||
@@ -47,81 +49,57 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
// Check for imports from DTO paths and enforce strict import rules
|
||||
// Check for imports from DTO paths
|
||||
ImportDeclaration(node) {
|
||||
const importPath = node.source.value;
|
||||
|
||||
// Check 1: Disallowed paths (DTO and service layers)
|
||||
// This catches ANY import from these paths, regardless of name
|
||||
if (importPath.includes('/lib/types/generated/') ||
|
||||
// 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/')) {
|
||||
|
||||
importPath.includes('/lib/services/')
|
||||
)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'noDtoImport',
|
||||
});
|
||||
}
|
||||
|
||||
// Check 2: Strict import path enforcement
|
||||
// Only allow imports from these specific paths
|
||||
const allowedPaths = [
|
||||
'@/lib/contracts/',
|
||||
'@/lib/view-models/',
|
||||
'@/lib/view-data/',
|
||||
'@/lib/formatters/',
|
||||
];
|
||||
|
||||
const isAllowed = allowedPaths.some(path => importPath.startsWith(path));
|
||||
const isRelativeImport = importPath.startsWith('.');
|
||||
const isExternal = !importPath.startsWith('.') && !importPath.startsWith('@');
|
||||
|
||||
// For relative imports, check if they contain allowed patterns
|
||||
// This is a heuristic - may need refinement based on project structure
|
||||
const isRelativeAllowed = isRelativeImport && (
|
||||
importPath.includes('/lib/contracts/') ||
|
||||
importPath.includes('/lib/view-models/') ||
|
||||
importPath.includes('/lib/view-data/') ||
|
||||
importPath.includes('/lib/formatters/') ||
|
||||
// Also check for patterns like ../contracts/...
|
||||
importPath.includes('contracts') ||
|
||||
importPath.includes('view-models') ||
|
||||
importPath.includes('view-data') ||
|
||||
importPath.includes('formatters') ||
|
||||
// Allow relative imports to view models (e.g., ./InvoiceViewModel, ../ViewModelName)
|
||||
// This matches patterns like ./ViewModelName or ../ViewModelName
|
||||
/^\.\/[A-Z][a-zA-Z0-9]*ViewModel$/.test(importPath) ||
|
||||
/^\.\.\/[A-Z][a-zA-Z0-9]*ViewModel$/.test(importPath)
|
||||
);
|
||||
|
||||
// Report if it's an internal import that's not allowed
|
||||
if (!isAllowed && !isRelativeAllowed && !isExternal) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'strictImport',
|
||||
data: { importPath },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Check for ViewData definitions (Interface or Type Alias)
|
||||
// Check for ViewData definitions in ViewModels
|
||||
TSInterfaceDeclaration(node) {
|
||||
if (node.id && node.id.name && node.id.name.endsWith('ViewData')) {
|
||||
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 (node.id && node.id.name && node.id.name.endsWith('ViewData')) {
|
||||
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',
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user