mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
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
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,5 +10,6 @@ export type {
|
||||
ArchitectureViolation,
|
||||
HardcodeViolation,
|
||||
CircularDependencyViolation,
|
||||
FrameworkLeakViolation,
|
||||
ProjectMetrics,
|
||||
} from "./api"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user