Files
puaros/packages/guardian/src/infrastructure/strategies/naming/AstInterfaceNameAnalyzer.ts

91 lines
3.0 KiB
TypeScript

import Parser from "tree-sitter"
import { NamingViolation } from "../../../domain/value-objects/NamingViolation"
import { AST_CLASS_TYPES, AST_FIELD_NAMES } from "../../../shared/constants"
import { LAYERS, NAMING_VIOLATION_TYPES } from "../../../shared/constants/rules"
import { NAMING_ERROR_MESSAGES, PATTERN_WORDS } from "../../constants/detectorPatterns"
/**
* AST-based analyzer for detecting interface naming violations
*
* Analyzes interface declaration nodes to ensure proper naming conventions:
* - Domain layer: Repository interfaces must start with 'I' (e.g., IUserRepository)
* - All layers: Interfaces should be PascalCase
*/
export class AstInterfaceNameAnalyzer {
/**
* Analyzes an interface declaration node
*/
public analyze(
node: Parser.SyntaxNode,
layer: string,
filePath: string,
_lines: string[],
): NamingViolation | null {
if (node.type !== AST_CLASS_TYPES.INTERFACE_DECLARATION) {
return null
}
const nameNode = node.childForFieldName(AST_FIELD_NAMES.NAME)
if (!nameNode) {
return null
}
const interfaceName = nameNode.text
const lineNumber = nameNode.startPosition.row + 1
if (!/^[A-Z][a-zA-Z0-9]*$/.test(interfaceName)) {
return NamingViolation.create(
interfaceName,
NAMING_VIOLATION_TYPES.WRONG_CASE,
layer,
`${filePath}:${String(lineNumber)}`,
NAMING_ERROR_MESSAGES.INTERFACE_PASCAL_CASE,
interfaceName,
NAMING_ERROR_MESSAGES.USE_PASCAL_CASE_INTERFACE,
)
}
if (layer === LAYERS.DOMAIN) {
return this.checkDomainInterface(interfaceName, filePath, lineNumber)
}
return null
}
/**
* Checks domain layer interface naming
*/
private checkDomainInterface(
interfaceName: string,
filePath: string,
lineNumber: number,
): NamingViolation | null {
if (interfaceName.endsWith(PATTERN_WORDS.REPOSITORY)) {
if (!interfaceName.startsWith(PATTERN_WORDS.I_PREFIX)) {
return NamingViolation.create(
interfaceName,
NAMING_VIOLATION_TYPES.WRONG_PREFIX,
LAYERS.DOMAIN,
`${filePath}:${String(lineNumber)}`,
NAMING_ERROR_MESSAGES.REPOSITORY_INTERFACE_I_PREFIX,
interfaceName,
`Rename to I${interfaceName}`,
)
}
if (!/^I[A-Z][a-zA-Z0-9]*Repository$/.test(interfaceName)) {
return NamingViolation.create(
interfaceName,
NAMING_VIOLATION_TYPES.WRONG_CASE,
LAYERS.DOMAIN,
`${filePath}:${String(lineNumber)}`,
NAMING_ERROR_MESSAGES.REPOSITORY_INTERFACE_PATTERN,
interfaceName,
)
}
}
return null
}
}