UNPKG

@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
"use strict"; 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