@opichi/smartcode
Version:
Universal code intelligence MCP server - analyze any codebase with TypeScript excellence and multi-language support
284 lines • 11.3 kB
JavaScript
export class PatternDetector {
graph;
projectStructure = null;
constructor(graph) {
this.graph = graph;
}
setProjectStructure(structure) {
this.projectStructure = structure;
}
detectPatterns() {
const patterns = [];
// Detect different architectural patterns
patterns.push(...this.detectMVCPattern());
patterns.push(...this.detectAPIPattern());
patterns.push(...this.detectServicePattern());
patterns.push(...this.detectRepositoryPattern());
patterns.push(...this.detectFactoryPattern());
patterns.push(...this.detectObserverPattern());
patterns.push(...this.detectSingletonPattern());
return patterns.filter(p => p.confidence > 0.3); // Only return confident patterns
}
detectMVCPattern() {
const patterns = [];
const components = this.graph.getArchitecturalComponents();
const models = components.get('models') || [];
const controllers = components.get('controllers') || [];
const views = components.get('views') || [];
if (models.length > 0 && controllers.length > 0) {
// Group related MVC triads
const mvcGroups = this.groupMVCComponents(models, controllers, views);
mvcGroups.forEach(group => {
patterns.push({
name: `${group.entity} MVC Pattern`,
type: 'mvc',
components: [...group.models, ...group.controllers, ...group.views],
description: `Model-View-Controller pattern for ${group.entity} entity`,
confidence: this.calculateMVCConfidence(group)
});
});
}
return patterns;
}
detectAPIPattern() {
const patterns = [];
const allNodes = this.graph.getNodesByType('function');
// Look for API endpoint patterns
const apiEndpoints = allNodes.filter(node => this.isAPIEndpoint(node));
if (apiEndpoints.length > 0) {
// Group by resource
const resourceGroups = this.groupAPIEndpoints(apiEndpoints);
resourceGroups.forEach((endpoints, resource) => {
patterns.push({
name: `${resource} REST API`,
type: 'api',
components: endpoints,
description: `RESTful API endpoints for ${resource} resource`,
confidence: this.calculateAPIConfidence(endpoints)
});
});
}
return patterns;
}
detectServicePattern() {
const patterns = [];
const services = this.graph.getArchitecturalComponents().get('services') || [];
if (services.length > 0) {
// Group services by domain
const serviceGroups = this.groupServicesByDomain(services);
serviceGroups.forEach((serviceList, domain) => {
patterns.push({
name: `${domain} Service Layer`,
type: 'service',
components: serviceList,
description: `Service layer pattern for ${domain} domain logic`,
confidence: this.calculateServiceConfidence(serviceList)
});
});
}
return patterns;
}
detectRepositoryPattern() {
const patterns = [];
const allNodes = this.graph.getNodesByType('class');
const repositories = allNodes.filter(node => node.name.toLowerCase().includes('repository') ||
node.name.toLowerCase().includes('dao') ||
this.hasRepositoryMethods(node));
if (repositories.length > 0) {
patterns.push({
name: 'Repository Pattern',
type: 'repository',
components: repositories,
description: 'Data access abstraction using repository pattern',
confidence: this.calculateRepositoryConfidence(repositories)
});
}
return patterns;
}
detectFactoryPattern() {
const patterns = [];
const allNodes = this.graph.getNodesByType('class');
const factories = allNodes.filter(node => node.name.toLowerCase().includes('factory') ||
node.name.toLowerCase().includes('builder') ||
this.hasFactoryMethods(node));
if (factories.length > 0) {
patterns.push({
name: 'Factory Pattern',
type: 'factory',
components: factories,
description: 'Object creation using factory pattern',
confidence: this.calculateFactoryConfidence(factories)
});
}
return patterns;
}
detectObserverPattern() {
const patterns = [];
const allNodes = this.graph.getNodesByType('class');
const observers = allNodes.filter(node => this.hasObserverMethods(node) ||
node.content.includes('addEventListener') ||
node.content.includes('subscribe') ||
node.content.includes('emit'));
if (observers.length > 0) {
patterns.push({
name: 'Observer Pattern',
type: 'observer',
components: observers,
description: 'Event-driven communication using observer pattern',
confidence: this.calculateObserverConfidence(observers)
});
}
return patterns;
}
detectSingletonPattern() {
const patterns = [];
const allNodes = this.graph.getNodesByType('class');
const singletons = allNodes.filter(node => this.hasSingletonPattern(node));
if (singletons.length > 0) {
patterns.push({
name: 'Singleton Pattern',
type: 'singleton',
components: singletons,
description: 'Single instance classes using singleton pattern',
confidence: this.calculateSingletonConfidence(singletons)
});
}
return patterns;
}
// Helper methods for pattern detection
groupMVCComponents(models, controllers, views) {
const groups = [];
const entities = new Set();
// Extract entity names from models and controllers
[...models, ...controllers].forEach(node => {
const entity = this.extractEntityName(node.name);
if (entity)
entities.add(entity);
});
entities.forEach(entity => {
const group = {
entity,
models: models.filter(m => this.extractEntityName(m.name) === entity),
controllers: controllers.filter(c => this.extractEntityName(c.name) === entity),
views: views.filter(v => this.extractEntityName(v.name) === entity)
};
if (group.models.length > 0 || group.controllers.length > 0) {
groups.push(group);
}
});
return groups;
}
extractEntityName(name) {
// Extract entity name from class/file names
const cleaned = name.toLowerCase()
.replace(/controller|model|view|service|repository/g, '')
.replace(/s$/, ''); // Remove plural
return cleaned.length > 1 ? cleaned : null;
}
isAPIEndpoint(node) {
const content = node.content.toLowerCase();
return content.includes('app.get') ||
content.includes('app.post') ||
content.includes('app.put') ||
content.includes('app.delete') ||
content.includes('router.') ||
content.includes('@route') ||
content.includes('route::');
}
groupAPIEndpoints(endpoints) {
const groups = new Map();
endpoints.forEach(endpoint => {
const resource = this.extractAPIResource(endpoint);
if (!groups.has(resource)) {
groups.set(resource, []);
}
groups.get(resource).push(endpoint);
});
return groups;
}
extractAPIResource(endpoint) {
// Extract resource name from API endpoint
const content = endpoint.content;
const routeMatch = content.match(/['"`]\/(\w+)/);
if (routeMatch) {
return routeMatch[1];
}
return this.extractEntityName(endpoint.name) || 'unknown';
}
groupServicesByDomain(services) {
const groups = new Map();
services.forEach(service => {
const domain = this.extractEntityName(service.name) || 'general';
if (!groups.has(domain)) {
groups.set(domain, []);
}
groups.get(domain).push(service);
});
return groups;
}
hasRepositoryMethods(node) {
const content = node.content.toLowerCase();
const repoMethods = ['find', 'save', 'delete', 'update', 'create', 'get', 'query'];
return repoMethods.some(method => content.includes(method));
}
hasFactoryMethods(node) {
const content = node.content.toLowerCase();
return content.includes('create') ||
content.includes('build') ||
content.includes('make') ||
content.includes('new ');
}
hasObserverMethods(node) {
const content = node.content.toLowerCase();
return content.includes('notify') ||
content.includes('observe') ||
content.includes('subscribe') ||
content.includes('unsubscribe') ||
content.includes('emit') ||
content.includes('trigger');
}
hasSingletonPattern(node) {
const content = node.content.toLowerCase();
return content.includes('instance') &&
(content.includes('static') || content.includes('private constructor'));
}
// Confidence calculation methods
calculateMVCConfidence(group) {
let confidence = 0.3; // Base confidence
if (group.models.length > 0)
confidence += 0.3;
if (group.controllers.length > 0)
confidence += 0.3;
if (group.views.length > 0)
confidence += 0.1;
return Math.min(confidence, 1.0);
}
calculateAPIConfidence(endpoints) {
const httpMethods = ['get', 'post', 'put', 'delete'];
const foundMethods = new Set();
endpoints.forEach(endpoint => {
httpMethods.forEach(method => {
if (endpoint.content.toLowerCase().includes(method)) {
foundMethods.add(method);
}
});
});
return Math.min(0.4 + (foundMethods.size * 0.15), 1.0);
}
calculateServiceConfidence(services) {
return Math.min(0.5 + (services.length * 0.1), 1.0);
}
calculateRepositoryConfidence(repositories) {
return Math.min(0.6 + (repositories.length * 0.1), 1.0);
}
calculateFactoryConfidence(factories) {
return Math.min(0.5 + (factories.length * 0.1), 1.0);
}
calculateObserverConfidence(observers) {
return Math.min(0.4 + (observers.length * 0.1), 1.0);
}
calculateSingletonConfidence(singletons) {
return Math.min(0.7 + (singletons.length * 0.1), 1.0);
}
}
//# sourceMappingURL=patterns.js.map