mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
fix: eliminate magic strings and fix aggregate boundary detection
- Extract DDD folder names and repository method suggestions to constants - Fix regex pattern to support relative paths (domain/... without leading /) - Add non-aggregate folder exclusions (constants, shared, factories, etc.) - Remove findAll, exists, count from ORM_QUERY_METHODS (valid domain methods) - Add exists, count, countBy patterns to domainMethodPatterns - Add aggregate boundary test examples
This commit is contained in:
@@ -5,6 +5,79 @@ All notable changes to @samiyev/guardian will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.7.1] - 2025-11-25
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 🐛 **Aggregate Boundary Detection for relative paths:**
|
||||||
|
- Fixed regex pattern to support paths starting with `domain/` (without leading `/`)
|
||||||
|
- Now correctly detects violations in projects scanned from parent directories
|
||||||
|
|
||||||
|
- 🐛 **Reduced false positives in Repository Pattern detection:**
|
||||||
|
- Removed `findAll`, `exists`, `count` from ORM technical methods blacklist
|
||||||
|
- These are now correctly recognized as valid domain method names
|
||||||
|
- Added `exists`, `count`, `countBy[A-Z]` to domain method patterns
|
||||||
|
|
||||||
|
- 🐛 **Non-aggregate folder exclusions:**
|
||||||
|
- Added exclusions for standard DDD folders: `constants`, `shared`, `factories`, `ports`, `interfaces`
|
||||||
|
- Prevents false positives when domain layer has shared utilities
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- ♻️ **Extracted magic strings to constants:**
|
||||||
|
- DDD folder names (`entities`, `aggregates`, `value-objects`, etc.) moved to `DDD_FOLDER_NAMES`
|
||||||
|
- Repository method suggestions moved to `REPOSITORY_METHOD_SUGGESTIONS`
|
||||||
|
- Fallback suggestions moved to `REPOSITORY_FALLBACK_SUGGESTIONS`
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- 📁 **Aggregate boundary test examples:**
|
||||||
|
- Added `examples/aggregate-boundary/domain/` with Order, User, Product aggregates
|
||||||
|
- Demonstrates cross-aggregate entity reference violations
|
||||||
|
|
||||||
|
## [0.7.0] - 2025-11-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
**🔒 Aggregate Boundary Validation**
|
||||||
|
|
||||||
|
New DDD feature to enforce aggregate boundaries and prevent tight coupling between aggregates.
|
||||||
|
|
||||||
|
- ✅ **Aggregate Boundary Detector:**
|
||||||
|
- Detects direct entity references across aggregate boundaries
|
||||||
|
- Validates that aggregates reference each other only by ID or Value Objects
|
||||||
|
- Supports multiple folder structure patterns:
|
||||||
|
- `domain/aggregates/order/Order.ts`
|
||||||
|
- `domain/order/Order.ts`
|
||||||
|
- `domain/entities/order/Order.ts`
|
||||||
|
|
||||||
|
- ✅ **Smart Import Analysis:**
|
||||||
|
- Parses ES6 imports and CommonJS require statements
|
||||||
|
- Identifies entity imports from other aggregates
|
||||||
|
- Allows imports from value-objects, events, services, specifications folders
|
||||||
|
|
||||||
|
- ✅ **Actionable Suggestions:**
|
||||||
|
- Reference by ID instead of entity
|
||||||
|
- Use Value Objects to store needed data from other aggregates
|
||||||
|
- Maintain aggregate independence
|
||||||
|
|
||||||
|
- ✅ **CLI Integration:**
|
||||||
|
- `--architecture` flag includes aggregate boundary checks
|
||||||
|
- CRITICAL severity for violations
|
||||||
|
- Detailed violation messages with file:line references
|
||||||
|
|
||||||
|
- ✅ **Test Coverage:**
|
||||||
|
- 41 new tests for aggregate boundary detection
|
||||||
|
- 333 total tests passing (100% pass rate)
|
||||||
|
- Examples in `examples/aggregate-boundary/`
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- New `AggregateBoundaryDetector` in infrastructure layer
|
||||||
|
- New `AggregateBoundaryViolation` value object in domain layer
|
||||||
|
- New `IAggregateBoundaryDetector` interface for dependency inversion
|
||||||
|
- Integrated into `AnalyzeProject` use case
|
||||||
|
|
||||||
## [0.6.4] - 2025-11-24
|
## [0.6.4] - 2025-11-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { User } from "../user/User"
|
||||||
|
import { Product } from "../product/Product"
|
||||||
|
|
||||||
|
export class Order {
|
||||||
|
private id: string
|
||||||
|
private user: User
|
||||||
|
private product: Product
|
||||||
|
private quantity: number
|
||||||
|
|
||||||
|
constructor(id: string, user: User, product: Product, quantity: number) {
|
||||||
|
this.id = id
|
||||||
|
this.user = user
|
||||||
|
this.product = product
|
||||||
|
this.quantity = quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export class Product {
|
||||||
|
public price: number
|
||||||
|
|
||||||
|
constructor(price: number) {
|
||||||
|
this.price = price
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export class User {
|
||||||
|
public email: string
|
||||||
|
|
||||||
|
constructor(email: string) {
|
||||||
|
this.email = email
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@samiyev/guardian",
|
"name": "@samiyev/guardian",
|
||||||
"version": "0.7.0",
|
"version": "0.7.1",
|
||||||
"description": "Research-backed code quality guardian for AI-assisted development. Detects hardcodes, circular deps, framework leaks, entity exposure, and 8 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, Windsurf, Claude, ChatGPT, Cline, and any AI coding tool.",
|
"description": "Research-backed code quality guardian for AI-assisted development. Detects hardcodes, circular deps, framework leaks, entity exposure, and 8 architecture violations. Enforces Clean Architecture/DDD principles. Works with GitHub Copilot, Cursor, Windsurf, Claude, ChatGPT, Cline, and any AI coding tool.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"puaros",
|
"puaros",
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ export const REPOSITORY_PATTERN_MESSAGES = {
|
|||||||
SUGGESTION_QUERY: "find or search",
|
SUGGESTION_QUERY: "find or search",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const REPOSITORY_FALLBACK_SUGGESTIONS = {
|
||||||
|
DEFAULT: "findById() or findByEmail()",
|
||||||
|
}
|
||||||
|
|
||||||
export const AGGREGATE_VIOLATION_MESSAGES = {
|
export const AGGREGATE_VIOLATION_MESSAGES = {
|
||||||
USE_ID_REFERENCE: "1. Reference other aggregates by ID (UserId, OrderId) instead of entity",
|
USE_ID_REFERENCE: "1. Reference other aggregates by ID (UserId, OrderId) instead of entity",
|
||||||
USE_VALUE_OBJECT:
|
USE_VALUE_OBJECT:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ValueObject } from "./ValueObject"
|
import { ValueObject } from "./ValueObject"
|
||||||
import { REPOSITORY_VIOLATION_TYPES } from "../../shared/constants/rules"
|
import { REPOSITORY_VIOLATION_TYPES } from "../../shared/constants/rules"
|
||||||
import { REPOSITORY_PATTERN_MESSAGES } from "../constants/Messages"
|
import { REPOSITORY_FALLBACK_SUGGESTIONS, REPOSITORY_PATTERN_MESSAGES } from "../constants/Messages"
|
||||||
|
|
||||||
interface RepositoryViolationProps {
|
interface RepositoryViolationProps {
|
||||||
readonly violationType:
|
readonly violationType:
|
||||||
@@ -192,7 +192,7 @@ export class RepositoryViolation extends ValueObject<RepositoryViolationProps> {
|
|||||||
const fallbackSuggestion =
|
const fallbackSuggestion =
|
||||||
technicalToDomain[this.props.methodName as keyof typeof technicalToDomain]
|
technicalToDomain[this.props.methodName as keyof typeof technicalToDomain]
|
||||||
const finalSuggestion =
|
const finalSuggestion =
|
||||||
smartSuggestion || fallbackSuggestion || "findById() or findByEmail()"
|
smartSuggestion || fallbackSuggestion || REPOSITORY_FALLBACK_SUGGESTIONS.DEFAULT
|
||||||
|
|
||||||
return [
|
return [
|
||||||
REPOSITORY_PATTERN_MESSAGES.STEP_RENAME_METHOD,
|
REPOSITORY_PATTERN_MESSAGES.STEP_RENAME_METHOD,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { IAggregateBoundaryDetector } from "../../domain/services/IAggregateBoun
|
|||||||
import { AggregateBoundaryViolation } from "../../domain/value-objects/AggregateBoundaryViolation"
|
import { AggregateBoundaryViolation } from "../../domain/value-objects/AggregateBoundaryViolation"
|
||||||
import { LAYERS } from "../../shared/constants/rules"
|
import { LAYERS } from "../../shared/constants/rules"
|
||||||
import { IMPORT_PATTERNS } from "../constants/paths"
|
import { IMPORT_PATTERNS } from "../constants/paths"
|
||||||
|
import { DDD_FOLDER_NAMES } from "../constants/detectorPatterns"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detects aggregate boundary violations in Domain-Driven Design
|
* Detects aggregate boundary violations in Domain-Driven Design
|
||||||
@@ -37,16 +38,37 @@ import { IMPORT_PATTERNS } from "../constants/paths"
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
||||||
private readonly entityFolderNames = new Set(["entities", "aggregates"])
|
private readonly entityFolderNames = new Set<string>([
|
||||||
private readonly valueObjectFolderNames = new Set(["value-objects", "vo"])
|
DDD_FOLDER_NAMES.ENTITIES,
|
||||||
private readonly allowedFolderNames = new Set([
|
DDD_FOLDER_NAMES.AGGREGATES,
|
||||||
"value-objects",
|
])
|
||||||
"vo",
|
private readonly valueObjectFolderNames = new Set<string>([
|
||||||
"events",
|
DDD_FOLDER_NAMES.VALUE_OBJECTS,
|
||||||
"domain-events",
|
DDD_FOLDER_NAMES.VO,
|
||||||
"repositories",
|
])
|
||||||
"services",
|
private readonly allowedFolderNames = new Set<string>([
|
||||||
"specifications",
|
DDD_FOLDER_NAMES.VALUE_OBJECTS,
|
||||||
|
DDD_FOLDER_NAMES.VO,
|
||||||
|
DDD_FOLDER_NAMES.EVENTS,
|
||||||
|
DDD_FOLDER_NAMES.DOMAIN_EVENTS,
|
||||||
|
DDD_FOLDER_NAMES.REPOSITORIES,
|
||||||
|
DDD_FOLDER_NAMES.SERVICES,
|
||||||
|
DDD_FOLDER_NAMES.SPECIFICATIONS,
|
||||||
|
])
|
||||||
|
private readonly nonAggregateFolderNames = new Set<string>([
|
||||||
|
DDD_FOLDER_NAMES.VALUE_OBJECTS,
|
||||||
|
DDD_FOLDER_NAMES.VO,
|
||||||
|
DDD_FOLDER_NAMES.EVENTS,
|
||||||
|
DDD_FOLDER_NAMES.DOMAIN_EVENTS,
|
||||||
|
DDD_FOLDER_NAMES.REPOSITORIES,
|
||||||
|
DDD_FOLDER_NAMES.SERVICES,
|
||||||
|
DDD_FOLDER_NAMES.SPECIFICATIONS,
|
||||||
|
DDD_FOLDER_NAMES.ENTITIES,
|
||||||
|
DDD_FOLDER_NAMES.CONSTANTS,
|
||||||
|
DDD_FOLDER_NAMES.SHARED,
|
||||||
|
DDD_FOLDER_NAMES.FACTORIES,
|
||||||
|
DDD_FOLDER_NAMES.PORTS,
|
||||||
|
DDD_FOLDER_NAMES.INTERFACES,
|
||||||
])
|
])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,12 +142,13 @@ export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
|||||||
public extractAggregateFromPath(filePath: string): string | undefined {
|
public extractAggregateFromPath(filePath: string): string | undefined {
|
||||||
const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/")
|
const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/")
|
||||||
|
|
||||||
const domainMatch = /\/domain\//.exec(normalizedPath)
|
const domainMatch = /(?:^|\/)(domain)\//.exec(normalizedPath)
|
||||||
if (!domainMatch) {
|
if (!domainMatch) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathAfterDomain = normalizedPath.substring(domainMatch.index + domainMatch[0].length)
|
const domainEndIndex = domainMatch.index + domainMatch[0].length
|
||||||
|
const pathAfterDomain = normalizedPath.substring(domainEndIndex)
|
||||||
const segments = pathAfterDomain.split("/").filter(Boolean)
|
const segments = pathAfterDomain.split("/").filter(Boolean)
|
||||||
|
|
||||||
if (segments.length < 2) {
|
if (segments.length < 2) {
|
||||||
@@ -136,10 +159,18 @@ export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
|||||||
if (segments.length < 3) {
|
if (segments.length < 3) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return segments[1]
|
const aggregate = segments[1]
|
||||||
|
if (this.nonAggregateFolderNames.has(aggregate)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return aggregate
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments[0]
|
const aggregate = segments[0]
|
||||||
|
if (this.nonAggregateFolderNames.has(aggregate)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return aggregate
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,11 +256,14 @@ export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < segments.length; i++) {
|
for (let i = 0; i < segments.length; i++) {
|
||||||
if (segments[i] === "domain" || segments[i] === "aggregates") {
|
if (
|
||||||
|
segments[i] === DDD_FOLDER_NAMES.DOMAIN ||
|
||||||
|
segments[i] === DDD_FOLDER_NAMES.AGGREGATES
|
||||||
|
) {
|
||||||
if (i + 1 < segments.length) {
|
if (i + 1 < segments.length) {
|
||||||
if (
|
if (
|
||||||
this.entityFolderNames.has(segments[i + 1]) ||
|
this.entityFolderNames.has(segments[i + 1]) ||
|
||||||
segments[i + 1] === "aggregates"
|
segments[i + 1] === DDD_FOLDER_NAMES.AGGREGATES
|
||||||
) {
|
) {
|
||||||
if (i + 2 < segments.length) {
|
if (i + 2 < segments.length) {
|
||||||
return segments[i + 2]
|
return segments[i + 2]
|
||||||
@@ -248,7 +282,7 @@ export class AggregateBoundaryDetector implements IAggregateBoundaryDetector {
|
|||||||
!this.entityFolderNames.has(secondLastSegment) &&
|
!this.entityFolderNames.has(secondLastSegment) &&
|
||||||
!this.valueObjectFolderNames.has(secondLastSegment) &&
|
!this.valueObjectFolderNames.has(secondLastSegment) &&
|
||||||
!this.allowedFolderNames.has(secondLastSegment) &&
|
!this.allowedFolderNames.has(secondLastSegment) &&
|
||||||
secondLastSegment !== "domain"
|
secondLastSegment !== DDD_FOLDER_NAMES.DOMAIN
|
||||||
) {
|
) {
|
||||||
return secondLastSegment
|
return secondLastSegment
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { RepositoryViolation } from "../../domain/value-objects/RepositoryViolat
|
|||||||
import { LAYERS, REPOSITORY_VIOLATION_TYPES } from "../../shared/constants/rules"
|
import { LAYERS, REPOSITORY_VIOLATION_TYPES } from "../../shared/constants/rules"
|
||||||
import { ORM_QUERY_METHODS } from "../constants/orm-methods"
|
import { ORM_QUERY_METHODS } from "../constants/orm-methods"
|
||||||
import { REPOSITORY_PATTERN_MESSAGES } from "../../domain/constants/Messages"
|
import { REPOSITORY_PATTERN_MESSAGES } from "../../domain/constants/Messages"
|
||||||
|
import { REPOSITORY_METHOD_SUGGESTIONS } from "../constants/detectorPatterns"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detects Repository Pattern violations in the codebase
|
* Detects Repository Pattern violations in the codebase
|
||||||
@@ -68,7 +69,7 @@ export class RepositoryPatternDetector implements IRepositoryPatternDetector {
|
|||||||
|
|
||||||
private readonly domainMethodPatterns = [
|
private readonly domainMethodPatterns = [
|
||||||
/^findBy[A-Z]/,
|
/^findBy[A-Z]/,
|
||||||
/^findAll/,
|
/^findAll$/,
|
||||||
/^find[A-Z]/,
|
/^find[A-Z]/,
|
||||||
/^save$/,
|
/^save$/,
|
||||||
/^saveAll$/,
|
/^saveAll$/,
|
||||||
@@ -83,11 +84,12 @@ export class RepositoryPatternDetector implements IRepositoryPatternDetector {
|
|||||||
/^add$/,
|
/^add$/,
|
||||||
/^add[A-Z]/,
|
/^add[A-Z]/,
|
||||||
/^get[A-Z]/,
|
/^get[A-Z]/,
|
||||||
/^getAll/,
|
/^getAll$/,
|
||||||
/^search/,
|
/^search/,
|
||||||
/^list/,
|
/^list/,
|
||||||
/^has[A-Z]/,
|
/^has[A-Z]/,
|
||||||
/^is[A-Z]/,
|
/^is[A-Z]/,
|
||||||
|
/^exists$/,
|
||||||
/^exists[A-Z]/,
|
/^exists[A-Z]/,
|
||||||
/^existsBy[A-Z]/,
|
/^existsBy[A-Z]/,
|
||||||
/^clear[A-Z]/,
|
/^clear[A-Z]/,
|
||||||
@@ -98,6 +100,8 @@ export class RepositoryPatternDetector implements IRepositoryPatternDetector {
|
|||||||
/^close$/,
|
/^close$/,
|
||||||
/^connect$/,
|
/^connect$/,
|
||||||
/^disconnect$/,
|
/^disconnect$/,
|
||||||
|
/^count$/,
|
||||||
|
/^countBy[A-Z]/,
|
||||||
]
|
]
|
||||||
|
|
||||||
private readonly concreteRepositoryPatterns = [
|
private readonly concreteRepositoryPatterns = [
|
||||||
@@ -254,15 +258,43 @@ export class RepositoryPatternDetector implements IRepositoryPatternDetector {
|
|||||||
const suggestions: string[] = []
|
const suggestions: string[] = []
|
||||||
|
|
||||||
const suggestionMap: Record<string, string[]> = {
|
const suggestionMap: Record<string, string[]> = {
|
||||||
query: ["search", "findBy[Property]"],
|
query: [
|
||||||
select: ["findBy[Property]", "get[Entity]"],
|
REPOSITORY_METHOD_SUGGESTIONS.SEARCH,
|
||||||
insert: ["create", "add[Entity]", "store[Entity]"],
|
REPOSITORY_METHOD_SUGGESTIONS.FIND_BY_PROPERTY,
|
||||||
update: ["update", "modify[Entity]"],
|
],
|
||||||
upsert: ["save", "store[Entity]"],
|
select: [
|
||||||
remove: ["delete", "removeBy[Property]"],
|
REPOSITORY_METHOD_SUGGESTIONS.FIND_BY_PROPERTY,
|
||||||
fetch: ["findBy[Property]", "get[Entity]"],
|
REPOSITORY_METHOD_SUGGESTIONS.GET_ENTITY,
|
||||||
retrieve: ["findBy[Property]", "get[Entity]"],
|
],
|
||||||
load: ["findBy[Property]", "get[Entity]"],
|
insert: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.CREATE,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.ADD_ENTITY,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.STORE_ENTITY,
|
||||||
|
],
|
||||||
|
update: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.UPDATE,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.MODIFY_ENTITY,
|
||||||
|
],
|
||||||
|
upsert: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.SAVE,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.STORE_ENTITY,
|
||||||
|
],
|
||||||
|
remove: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.DELETE,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.REMOVE_BY_PROPERTY,
|
||||||
|
],
|
||||||
|
fetch: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.FIND_BY_PROPERTY,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.GET_ENTITY,
|
||||||
|
],
|
||||||
|
retrieve: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.FIND_BY_PROPERTY,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.GET_ENTITY,
|
||||||
|
],
|
||||||
|
load: [
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.FIND_BY_PROPERTY,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.GET_ENTITY,
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [keyword, keywords] of Object.entries(suggestionMap)) {
|
for (const [keyword, keywords] of Object.entries(suggestionMap)) {
|
||||||
@@ -272,11 +304,14 @@ export class RepositoryPatternDetector implements IRepositoryPatternDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lowerName.includes("get") && lowerName.includes("all")) {
|
if (lowerName.includes("get") && lowerName.includes("all")) {
|
||||||
suggestions.push("findAll", "listAll")
|
suggestions.push(
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.FIND_ALL,
|
||||||
|
REPOSITORY_METHOD_SUGGESTIONS.LIST_ALL,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (suggestions.length === 0) {
|
if (suggestions.length === 0) {
|
||||||
return "Use domain-specific names like: findBy[Property], save, create, delete, update, add[Entity]"
|
return REPOSITORY_METHOD_SUGGESTIONS.DEFAULT_SUGGESTION
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Consider: ${suggestions.slice(0, 3).join(", ")}`
|
return `Consider: ${suggestions.slice(0, 3).join(", ")}`
|
||||||
|
|||||||
@@ -64,3 +64,45 @@ export const NAMING_ERROR_MESSAGES = {
|
|||||||
USE_VERB_NOUN: "Use verb + noun in PascalCase (e.g., CreateUser.ts, UpdateProfile.ts)",
|
USE_VERB_NOUN: "Use verb + noun in PascalCase (e.g., CreateUser.ts, UpdateProfile.ts)",
|
||||||
USE_CASE_START_VERB: "Use cases should start with a verb",
|
USE_CASE_START_VERB: "Use cases should start with a verb",
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DDD folder names for aggregate boundary detection
|
||||||
|
*/
|
||||||
|
export const DDD_FOLDER_NAMES = {
|
||||||
|
ENTITIES: "entities",
|
||||||
|
AGGREGATES: "aggregates",
|
||||||
|
VALUE_OBJECTS: "value-objects",
|
||||||
|
VO: "vo",
|
||||||
|
EVENTS: "events",
|
||||||
|
DOMAIN_EVENTS: "domain-events",
|
||||||
|
REPOSITORIES: "repositories",
|
||||||
|
SERVICES: "services",
|
||||||
|
SPECIFICATIONS: "specifications",
|
||||||
|
DOMAIN: "domain",
|
||||||
|
CONSTANTS: "constants",
|
||||||
|
SHARED: "shared",
|
||||||
|
FACTORIES: "factories",
|
||||||
|
PORTS: "ports",
|
||||||
|
INTERFACES: "interfaces",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository method suggestions for domain language
|
||||||
|
*/
|
||||||
|
export const REPOSITORY_METHOD_SUGGESTIONS = {
|
||||||
|
SEARCH: "search",
|
||||||
|
FIND_BY_PROPERTY: "findBy[Property]",
|
||||||
|
GET_ENTITY: "get[Entity]",
|
||||||
|
CREATE: "create",
|
||||||
|
ADD_ENTITY: "add[Entity]",
|
||||||
|
STORE_ENTITY: "store[Entity]",
|
||||||
|
UPDATE: "update",
|
||||||
|
MODIFY_ENTITY: "modify[Entity]",
|
||||||
|
SAVE: "save",
|
||||||
|
DELETE: "delete",
|
||||||
|
REMOVE_BY_PROPERTY: "removeBy[Property]",
|
||||||
|
FIND_ALL: "findAll",
|
||||||
|
LIST_ALL: "listAll",
|
||||||
|
DEFAULT_SUGGESTION:
|
||||||
|
"Use domain-specific names like: findBy[Property], save, create, delete, update, add[Entity]",
|
||||||
|
} as const
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export const ORM_QUERY_METHODS = [
|
|||||||
"findOne",
|
"findOne",
|
||||||
"findMany",
|
"findMany",
|
||||||
"findFirst",
|
"findFirst",
|
||||||
"findAll",
|
|
||||||
"findAndCountAll",
|
"findAndCountAll",
|
||||||
"insert",
|
"insert",
|
||||||
"insertMany",
|
"insertMany",
|
||||||
@@ -17,8 +16,6 @@ export const ORM_QUERY_METHODS = [
|
|||||||
"run",
|
"run",
|
||||||
"exec",
|
"exec",
|
||||||
"aggregate",
|
"aggregate",
|
||||||
"count",
|
|
||||||
"exists",
|
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type OrmQueryMethod = (typeof ORM_QUERY_METHODS)[number]
|
export type OrmQueryMethod = (typeof ORM_QUERY_METHODS)[number]
|
||||||
|
|||||||
Reference in New Issue
Block a user