@samiyev/guardian
Version:
Research-backed code quality guardian for AI-assisted development. Detects hardcodes, secrets, circular deps, framework leaks, entity exposure, and 9 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, W
126 lines • 7.01 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AstClassNameAnalyzer = void 0;
const NamingViolation_1 = require("../../../domain/value-objects/NamingViolation");
const constants_1 = require("../../../shared/constants");
const rules_1 = require("../../../shared/constants/rules");
const detectorPatterns_1 = require("../../constants/detectorPatterns");
/**
* AST-based analyzer for detecting class naming violations
*
* Analyzes class declaration nodes to ensure proper naming conventions:
* - Domain layer: PascalCase entities and services (*Service)
* - Application layer: PascalCase use cases (Verb+Noun), DTOs (*Dto/*Request/*Response)
* - Infrastructure layer: PascalCase controllers, repositories, services
*/
class AstClassNameAnalyzer {
/**
* Analyzes a class declaration node
*/
analyze(node, layer, filePath, _lines) {
if (node.type !== constants_1.AST_CLASS_TYPES.CLASS_DECLARATION) {
return null;
}
const nameNode = node.childForFieldName(constants_1.AST_FIELD_NAMES.NAME);
if (!nameNode) {
return null;
}
const className = nameNode.text;
const lineNumber = nameNode.startPosition.row + 1;
switch (layer) {
case rules_1.LAYERS.DOMAIN:
return this.checkDomainClass(className, filePath, lineNumber);
case rules_1.LAYERS.APPLICATION:
return this.checkApplicationClass(className, filePath, lineNumber);
case rules_1.LAYERS.INFRASTRUCTURE:
return this.checkInfrastructureClass(className, filePath, lineNumber);
default:
return null;
}
}
/**
* Checks domain layer class naming
*/
checkDomainClass(className, filePath, lineNumber) {
if (className.endsWith(detectorPatterns_1.FILE_SUFFIXES.SERVICE.replace(".ts", ""))) {
if (!/^[A-Z][a-zA-Z0-9]*Service$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_CASE, rules_1.LAYERS.DOMAIN, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.DOMAIN_SERVICE_PASCAL_CASE, className);
}
return null;
}
if (!/^[A-Z][a-zA-Z0-9]*$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_CASE, rules_1.LAYERS.DOMAIN, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.DOMAIN_ENTITY_PASCAL_CASE, className, detectorPatterns_1.NAMING_ERROR_MESSAGES.USE_PASCAL_CASE);
}
return null;
}
/**
* Checks application layer class naming
*/
checkApplicationClass(className, filePath, lineNumber) {
if (className.endsWith("Dto") ||
className.endsWith("Request") ||
className.endsWith("Response")) {
if (!/^[A-Z][a-zA-Z0-9]*(Dto|Request|Response)$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_SUFFIX, rules_1.LAYERS.APPLICATION, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.DTO_PASCAL_CASE, className, detectorPatterns_1.NAMING_ERROR_MESSAGES.USE_DTO_SUFFIX);
}
return null;
}
if (className.endsWith("Mapper")) {
if (!/^[A-Z][a-zA-Z0-9]*Mapper$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_SUFFIX, rules_1.LAYERS.APPLICATION, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.MAPPER_PASCAL_CASE, className);
}
return null;
}
const startsWithVerb = this.startsWithCommonVerb(className);
const startsWithLowercaseVerb = this.startsWithLowercaseVerb(className);
if (startsWithVerb) {
if (!/^[A-Z][a-z]+[A-Z][a-zA-Z0-9]*$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_VERB_NOUN, rules_1.LAYERS.APPLICATION, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.USE_CASE_VERB_NOUN, className, detectorPatterns_1.NAMING_ERROR_MESSAGES.USE_VERB_NOUN);
}
}
else if (startsWithLowercaseVerb) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_VERB_NOUN, rules_1.LAYERS.APPLICATION, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.USE_CASE_VERB_NOUN, className, detectorPatterns_1.NAMING_ERROR_MESSAGES.USE_VERB_NOUN);
}
return null;
}
/**
* Checks infrastructure layer class naming
*/
checkInfrastructureClass(className, filePath, lineNumber) {
if (className.endsWith("Controller")) {
if (!/^[A-Z][a-zA-Z0-9]*Controller$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_SUFFIX, rules_1.LAYERS.INFRASTRUCTURE, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.CONTROLLER_PASCAL_CASE, className);
}
return null;
}
if (className.endsWith(detectorPatterns_1.PATTERN_WORDS.REPOSITORY) &&
!className.startsWith(detectorPatterns_1.PATTERN_WORDS.I_PREFIX)) {
if (!/^[A-Z][a-zA-Z0-9]*Repository$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_SUFFIX, rules_1.LAYERS.INFRASTRUCTURE, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.REPOSITORY_IMPL_PASCAL_CASE, className);
}
return null;
}
if (className.endsWith("Service") || className.endsWith("Adapter")) {
if (!/^[A-Z][a-zA-Z0-9]*(Service|Adapter)$/.test(className)) {
return NamingViolation_1.NamingViolation.create(className, rules_1.NAMING_VIOLATION_TYPES.WRONG_SUFFIX, rules_1.LAYERS.INFRASTRUCTURE, `${filePath}:${String(lineNumber)}`, detectorPatterns_1.NAMING_ERROR_MESSAGES.SERVICE_ADAPTER_PASCAL_CASE, className);
}
return null;
}
return null;
}
/**
* Checks if class name starts with a common use case verb
*/
startsWithCommonVerb(className) {
return rules_1.USE_CASE_VERBS.some((verb) => className.startsWith(verb));
}
/**
* Checks if class name starts with a lowercase verb (camelCase use case)
*/
startsWithLowercaseVerb(className) {
const lowercaseVerbs = rules_1.USE_CASE_VERBS.map((verb) => verb.toLowerCase());
return lowercaseVerbs.some((verb) => className.startsWith(verb));
}
}
exports.AstClassNameAnalyzer = AstClassNameAnalyzer;
//# sourceMappingURL=AstClassNameAnalyzer.js.map