website refactor
This commit is contained in:
@@ -161,6 +161,9 @@ export class ArchitectureGuardrails {
|
||||
this.checkSortingFiltering(filePath, content);
|
||||
this.checkNullReturns(filePath, content);
|
||||
|
||||
// NEW: Enforce PageQuery contract
|
||||
this.checkPageQueryContract(filePath, content);
|
||||
|
||||
// Rule 8: Forbid 'as any' usage
|
||||
this.checkAsAnyUsage(filePath, content);
|
||||
|
||||
@@ -226,6 +229,9 @@ export class ArchitectureGuardrails {
|
||||
this.checkUseClientDirective(filePath, content);
|
||||
this.checkViewModelPageQueryImports(filePath, content);
|
||||
|
||||
// NEW: Enforce ViewModel contract
|
||||
this.checkViewModelContract(filePath, content);
|
||||
|
||||
// Rule 8: Forbid 'as any' usage
|
||||
this.checkAsAnyUsage(filePath, content);
|
||||
|
||||
@@ -243,6 +249,9 @@ export class ArchitectureGuardrails {
|
||||
// Rule 6: Client-only guardrails - presenters should not use HTTP
|
||||
this.checkHttpCalls(filePath, content);
|
||||
|
||||
// NEW: Enforce Presenter contract
|
||||
this.checkPresenterContract(filePath, content);
|
||||
|
||||
// Rule 8: Forbid 'as any' usage
|
||||
this.checkAsAnyUsage(filePath, content);
|
||||
|
||||
@@ -1078,6 +1087,109 @@ export class ArchitectureGuardrails {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NEW: VIOLATION CHECKS - CONTRACT ENFORCEMENT
|
||||
// ============================================================================
|
||||
|
||||
private checkPageQueryContract(filePath: string, content: string): void {
|
||||
// Check if file contains a class that should implement PageQuery
|
||||
const classMatch = content.match(/class\s+(\w+PageQuery)\s+{/);
|
||||
if (!classMatch) return;
|
||||
|
||||
// Check if it implements PageQuery interface
|
||||
if (!content.includes('implements PageQuery<')) {
|
||||
this.addViolation(
|
||||
'pagequery-must-implement-contract',
|
||||
filePath,
|
||||
1,
|
||||
'PageQuery class must implement PageQuery<TPageDto, TParams> interface'
|
||||
);
|
||||
}
|
||||
|
||||
// Check if it has execute method
|
||||
if (!content.includes('execute(')) {
|
||||
this.addViolation(
|
||||
'pagequery-must-have-execute',
|
||||
filePath,
|
||||
1,
|
||||
'PageQuery class must have execute(params) method'
|
||||
);
|
||||
}
|
||||
|
||||
// Check if execute returns Promise<PageQueryResult>
|
||||
const executeMatch = content.match(/execute\([^)]*\):\s*Promise<PageQueryResult/);
|
||||
if (!executeMatch) {
|
||||
this.addViolation(
|
||||
'pagequery-execute-return-type',
|
||||
filePath,
|
||||
1,
|
||||
'PageQuery execute() must return Promise<PageQueryResult<TPageDto>>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private checkPresenterContract(filePath: string, content: string): void {
|
||||
// Check if file contains a class that should implement Presenter
|
||||
const classMatch = content.match(/class\s+(\w+Presenter)\s+{/);
|
||||
if (!classMatch) return;
|
||||
|
||||
// Check if it implements Presenter interface
|
||||
if (!content.includes('implements Presenter<')) {
|
||||
this.addViolation(
|
||||
'presenter-must-implement-contract',
|
||||
filePath,
|
||||
1,
|
||||
'Presenter class must implement Presenter<TInput, TOutput> interface'
|
||||
);
|
||||
}
|
||||
|
||||
// Check if it has present method
|
||||
if (!content.includes('present(')) {
|
||||
this.addViolation(
|
||||
'presenter-must-have-present',
|
||||
filePath,
|
||||
1,
|
||||
'Presenter class must have present(input) method'
|
||||
);
|
||||
}
|
||||
|
||||
// Check for 'use client' directive
|
||||
if (!content.includes("'use client'") && !content.includes('"use client"')) {
|
||||
this.addViolation(
|
||||
'presenter-must-be-client',
|
||||
filePath,
|
||||
1,
|
||||
'Presenter must have \'use client\' directive at top-level'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private checkViewModelContract(filePath: string, content: string): void {
|
||||
// Check if file contains a class that should extend ViewModel
|
||||
const classMatch = content.match(/class\s+(\w+ViewModel)\s+{/);
|
||||
if (!classMatch) return;
|
||||
|
||||
// Check if it extends ViewModel
|
||||
if (!content.includes('extends ViewModel')) {
|
||||
this.addViolation(
|
||||
'viewmodel-must-extend-contract',
|
||||
filePath,
|
||||
1,
|
||||
'ViewModel class must extend ViewModel base class'
|
||||
);
|
||||
}
|
||||
|
||||
// Check for 'use client' directive
|
||||
if (!content.includes("'use client'") && !content.includes('"use client"')) {
|
||||
this.addViolation(
|
||||
'viewmodel-must-be-client',
|
||||
filePath,
|
||||
1,
|
||||
'ViewModel must have \'use client\' directive at top-level'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HELPERS
|
||||
// ============================================================================
|
||||
|
||||
@@ -139,4 +139,42 @@ describe('Architecture Guardrails', () => {
|
||||
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
|
||||
// NEW: Contract enforcement tests
|
||||
it('should enforce: PageQuery classes must implement PageQuery contract', () => {
|
||||
const violations = guardrails.getFilteredViolations().filter(
|
||||
v => v.ruleName === 'pagequery-must-implement-contract' ||
|
||||
v.ruleName === 'pagequery-must-have-execute' ||
|
||||
v.ruleName === 'pagequery-execute-return-type'
|
||||
);
|
||||
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should enforce: Presenter classes must implement Presenter contract', () => {
|
||||
const violations = guardrails.getFilteredViolations().filter(
|
||||
v => v.ruleName === 'presenter-must-implement-contract' ||
|
||||
v.ruleName === 'presenter-must-have-present' ||
|
||||
v.ruleName === 'presenter-must-be-client'
|
||||
);
|
||||
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should enforce: ViewModel classes must extend ViewModel contract', () => {
|
||||
const violations = guardrails.getFilteredViolations().filter(
|
||||
v => v.ruleName === 'viewmodel-must-extend-contract' ||
|
||||
v.ruleName === 'viewmodel-must-be-client'
|
||||
);
|
||||
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should enforce: DisplayObject files must export only classes', () => {
|
||||
const violations = guardrails.getFilteredViolations().filter(
|
||||
v => v.ruleName === 'no-non-class-display-exports'
|
||||
);
|
||||
|
||||
expect(violations.length).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user