From 19abff30f0be749b925218487bd03337fb4e797e Mon Sep 17 00:00:00 2001 From: imfozilbek Date: Mon, 24 Nov 2025 12:53:50 +0500 Subject: [PATCH] feat: integrate framework leak detection into analysis pipeline - Add framework leak detector to AnalyzeProject use case - Export FrameworkLeakDetector in public API - Add FRAMEWORK_LEAK rule constant - Include framework leak violations in analysis response --- packages/guardian/src/api.ts | 5 + .../application/use-cases/AnalyzeProject.ts | 45 +++ packages/guardian/src/index.ts | 1 + .../guardian/src/shared/constants/rules.ts | 272 ++++++++++++++++++ 4 files changed, 323 insertions(+) diff --git a/packages/guardian/src/api.ts b/packages/guardian/src/api.ts index 82c892a..966283d 100644 --- a/packages/guardian/src/api.ts +++ b/packages/guardian/src/api.ts @@ -7,10 +7,12 @@ import { IFileScanner } from "./domain/services/IFileScanner" import { ICodeParser } from "./domain/services/ICodeParser" import { IHardcodeDetector } from "./domain/services/IHardcodeDetector" import { INamingConventionDetector } from "./domain/services/INamingConventionDetector" +import { IFrameworkLeakDetector } from "./domain/services/IFrameworkLeakDetector" import { FileScanner } from "./infrastructure/scanners/FileScanner" import { CodeParser } from "./infrastructure/parsers/CodeParser" import { HardcodeDetector } from "./infrastructure/analyzers/HardcodeDetector" import { NamingConventionDetector } from "./infrastructure/analyzers/NamingConventionDetector" +import { FrameworkLeakDetector } from "./infrastructure/analyzers/FrameworkLeakDetector" import { ERROR_MESSAGES } from "./shared/constants" /** @@ -63,11 +65,13 @@ export async function analyzeProject( const codeParser: ICodeParser = new CodeParser() const hardcodeDetector: IHardcodeDetector = new HardcodeDetector() const namingConventionDetector: INamingConventionDetector = new NamingConventionDetector() + const frameworkLeakDetector: IFrameworkLeakDetector = new FrameworkLeakDetector() const useCase = new AnalyzeProject( fileScanner, codeParser, hardcodeDetector, namingConventionDetector, + frameworkLeakDetector, ) const result = await useCase.execute(options) @@ -86,5 +90,6 @@ export type { HardcodeViolation, CircularDependencyViolation, NamingConventionViolation, + FrameworkLeakViolation, ProjectMetrics, } from "./application/use-cases/AnalyzeProject" diff --git a/packages/guardian/src/application/use-cases/AnalyzeProject.ts b/packages/guardian/src/application/use-cases/AnalyzeProject.ts index cbe5f2c..1253fc1 100644 --- a/packages/guardian/src/application/use-cases/AnalyzeProject.ts +++ b/packages/guardian/src/application/use-cases/AnalyzeProject.ts @@ -4,6 +4,7 @@ import { IFileScanner } from "../../domain/services/IFileScanner" import { ICodeParser } from "../../domain/services/ICodeParser" import { IHardcodeDetector } from "../../domain/services/IHardcodeDetector" import { INamingConventionDetector } from "../../domain/services/INamingConventionDetector" +import { IFrameworkLeakDetector } from "../../domain/services/IFrameworkLeakDetector" import { SourceFile } from "../../domain/entities/SourceFile" import { DependencyGraph } from "../../domain/entities/DependencyGraph" import { ProjectPath } from "../../domain/value-objects/ProjectPath" @@ -30,6 +31,7 @@ export interface AnalyzeProjectResponse { hardcodeViolations: HardcodeViolation[] circularDependencyViolations: CircularDependencyViolation[] namingViolations: NamingConventionViolation[] + frameworkLeakViolations: FrameworkLeakViolation[] metrics: ProjectMetrics } @@ -81,6 +83,18 @@ export interface NamingConventionViolation { suggestion?: string } +export interface FrameworkLeakViolation { + rule: typeof RULES.FRAMEWORK_LEAK + packageName: string + category: string + categoryDescription: string + file: string + layer: string + line?: number + message: string + suggestion: string +} + export interface ProjectMetrics { totalFiles: number totalFunctions: number @@ -100,6 +114,7 @@ export class AnalyzeProject extends UseCase< private readonly codeParser: ICodeParser, private readonly hardcodeDetector: IHardcodeDetector, private readonly namingConventionDetector: INamingConventionDetector, + private readonly frameworkLeakDetector: IFrameworkLeakDetector, ) { super() } @@ -148,6 +163,7 @@ export class AnalyzeProject extends UseCase< const hardcodeViolations = this.detectHardcode(sourceFiles) const circularDependencyViolations = this.detectCircularDependencies(dependencyGraph) const namingViolations = this.detectNamingConventions(sourceFiles) + const frameworkLeakViolations = this.detectFrameworkLeaks(sourceFiles) const metrics = this.calculateMetrics(sourceFiles, totalFunctions, dependencyGraph) return ResponseDto.ok({ @@ -157,6 +173,7 @@ export class AnalyzeProject extends UseCase< hardcodeViolations, circularDependencyViolations, namingViolations, + frameworkLeakViolations, metrics, }) } catch (error) { @@ -319,6 +336,34 @@ export class AnalyzeProject extends UseCase< return violations } + private detectFrameworkLeaks(sourceFiles: SourceFile[]): FrameworkLeakViolation[] { + const violations: FrameworkLeakViolation[] = [] + + for (const file of sourceFiles) { + const leaks = this.frameworkLeakDetector.detectLeaks( + file.imports, + file.path.relative, + file.layer, + ) + + for (const leak of leaks) { + violations.push({ + rule: RULES.FRAMEWORK_LEAK, + packageName: leak.packageName, + category: leak.category, + categoryDescription: leak.getCategoryDescription(), + file: file.path.relative, + layer: leak.layer, + line: leak.line, + message: leak.getMessage(), + suggestion: leak.getSuggestion(), + }) + } + } + + return violations + } + private calculateMetrics( sourceFiles: SourceFile[], totalFunctions: number, diff --git a/packages/guardian/src/index.ts b/packages/guardian/src/index.ts index 94d3212..d383ab2 100644 --- a/packages/guardian/src/index.ts +++ b/packages/guardian/src/index.ts @@ -10,5 +10,6 @@ export type { ArchitectureViolation, HardcodeViolation, CircularDependencyViolation, + FrameworkLeakViolation, ProjectMetrics, } from "./api" diff --git a/packages/guardian/src/shared/constants/rules.ts b/packages/guardian/src/shared/constants/rules.ts index 61ae9d8..e0198fc 100644 --- a/packages/guardian/src/shared/constants/rules.ts +++ b/packages/guardian/src/shared/constants/rules.ts @@ -6,6 +6,7 @@ export const RULES = { HARDCODED_VALUE: "hardcoded-value", CIRCULAR_DEPENDENCY: "circular-dependency", NAMING_CONVENTION: "naming-convention", + FRAMEWORK_LEAK: "framework-leak", } as const /** @@ -124,3 +125,274 @@ export const USE_CASE_VERBS = [ "Reject", "Confirm", ] as const + +/** + * Framework-specific packages that should not be imported in domain layer + * These frameworks create tight coupling and violate Clean Architecture principles + */ +export const FRAMEWORK_PACKAGES = { + ORM: [ + "@prisma/client", + "prisma", + "typeorm", + "mongoose", + "sequelize", + "@mikro-orm/core", + "@mikro-orm/mongodb", + "@mikro-orm/postgresql", + "drizzle-orm", + "knex", + "objection", + "bookshelf", + "waterline", + "massive", + "pg", + "mysql", + "mysql2", + "sqlite3", + "better-sqlite3", + "mongodb", + "monk", + "tingodb", + "nedb", + "levelup", + "cassandra-driver", + "couchbase", + "redis-om", + ], + WEB_FRAMEWORK: [ + "express", + "fastify", + "koa", + "@koa/router", + "koa-router", + "@nestjs/common", + "@nestjs/core", + "@nestjs/platform-express", + "@nestjs/platform-fastify", + "hapi", + "@hapi/hapi", + "restify", + "polka", + "micro", + "next", + "nuxt", + "sails", + "adonis", + "@adonisjs/core", + "loopback", + "@loopback/core", + "feathers", + "@feathersjs/feathers", + "meteor", + "strapi", + "@strapi/strapi", + "total.js", + "actionhero", + ], + HTTP_CLIENT: [ + "axios", + "node-fetch", + "got", + "superagent", + "request", + "request-promise", + "request-promise-native", + "needle", + "bent", + "phin", + "ky", + "undici", + "@apollo/client", + "graphql-request", + "urql", + "isomorphic-fetch", + "cross-fetch", + "fetch-retry", + "wretch", + "httpie", + ], + VALIDATION: [ + "joi", + "yup", + "zod", + "class-validator", + "ajv", + "validator", + "express-validator", + "celebrate", + "superstruct", + "io-ts", + "runtypes", + "valibot", + "fastest-validator", + "validatorjs", + "vine", + "@vinejs/vine", + "vest", + "json-schema", + "jsonschema", + ], + DI_CONTAINER: [ + "inversify", + "tsyringe", + "awilix", + "typedi", + "bottlejs", + "injection-js", + "vue-di", + "angular", + "@angular/core", + "di-ts", + "power-di", + ], + LOGGER: [ + "winston", + "pino", + "bunyan", + "log4js", + "morgan", + "signale", + "consola", + "roarr", + "loglevel", + "debug", + "npmlog", + "@nestjs/logger", + "fancy-log", + "tracer", + "electron-log", + "simple-node-logger", + ], + CACHE: [ + "redis", + "ioredis", + "memcached", + "node-cache", + "cache-manager", + "keyv", + "flat-cache", + "lru-cache", + "node-cache-manager", + "quick-lru", + "mem", + "memoizee", + "micro-memoize", + "async-cache", + "cacache", + ], + MESSAGE_QUEUE: [ + "amqplib", + "bull", + "bullmq", + "bee-queue", + "kafkajs", + "rabbitmq", + "amqp", + "aws-sdk", + "@aws-sdk/client-sqs", + "@aws-sdk/client-sns", + "@azure/service-bus", + "azure-sb", + "@google-cloud/pubsub", + "rsmq", + "mqtt", + "rhea", + "stompit", + "activemq", + "zeromq", + "nanomsg", + "kue", + "agenda", + "bree", + ], + EMAIL: [ + "nodemailer", + "sendgrid", + "@sendgrid/mail", + "mailgun-js", + "mailgun.js", + "postmark", + "sparkpost", + "ses", + "@aws-sdk/client-ses", + "emailjs", + "email-templates", + "mjml", + "pug", + "handlebars", + "sendmail", + "smtp-server", + "mailparser", + "imap", + "imap-simple", + ], + STORAGE: [ + "aws-sdk", + "@aws-sdk/client-s3", + "@aws-sdk/s3-request-presigner", + "multer", + "multer-s3", + "@google-cloud/storage", + "@azure/storage-blob", + "azure-storage", + "minio", + "formidable", + "busboy", + "multiparty", + "express-fileupload", + "gridfs-stream", + "s3-upload-stream", + "knox", + "pkgcloud", + "@supabase/storage-js", + "cloudinary", + ], + TESTING: [ + "jest", + "@jest/globals", + "mocha", + "chai", + "sinon", + "supertest", + "cypress", + "@cypress/vue", + "playwright", + "@playwright/test", + "vitest", + "@vitest/ui", + "ava", + "tap", + "tape", + "jasmine", + "@testing-library/react", + "@testing-library/vue", + "enzyme", + ], + TEMPLATE_ENGINE: [ + "ejs", + "pug", + "jade", + "handlebars", + "mustache", + "nunjucks", + "dot", + "underscore", + "lodash.template", + "hogan.js", + "swig", + "twig", + "marko", + "squirrelly", + "eta", + ], +} as const + +/** + * Error messages for framework leak violations + */ +export const FRAMEWORK_LEAK_MESSAGES = { + DOMAIN_IMPORT: + 'Domain layer imports framework-specific package "{package}". Use interfaces and dependency injection instead.', + SUGGESTION: "Create an interface in domain layer and implement it in infrastructure layer.", +} as const