smartui-migration-tool
Version:
Enterprise-grade CLI tool for migrating visual testing platforms to LambdaTest SmartUI
668 lines • 28.8 kB
JavaScript
"use strict";
/**
* Intelligent Suggestion Engine
* Phase 3: Cross-File Dependency Analysis & Intelligent Suggestions
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.IntelligentSuggestionEngine = void 0;
class IntelligentSuggestionEngine {
constructor() {
this.rules = new Map();
this.statistics = this.initializeStatistics();
this.metadata = this.initializeMetadata();
this.initializeBuiltInRules();
}
initializeStatistics() {
return {
totalSuggestions: 0,
highPrioritySuggestions: 0,
mediumPrioritySuggestions: 0,
lowPrioritySuggestions: 0,
criticalSuggestions: 0,
highEffortSuggestions: 0,
mediumEffortSuggestions: 0,
lowEffortSuggestions: 0,
highImpactSuggestions: 0,
mediumImpactSuggestions: 0,
lowImpactSuggestions: 0,
highRiskSuggestions: 0,
mediumRiskSuggestions: 0,
lowRiskSuggestions: 0,
highConfidenceSuggestions: 0,
mediumConfidenceSuggestions: 0,
lowConfidenceSuggestions: 0,
averageConfidence: 0,
averagePriority: 0,
averageEffort: 0,
averageImpact: 0,
averageRisk: 0,
processingTime: 0,
memoryUsage: 0
};
}
initializeMetadata() {
return {
language: 'javascript',
framework: null,
platform: null,
version: '1.0.0',
timestamp: new Date().toISOString(),
processingTime: 0,
memoryUsage: 0,
confidence: 0.8,
quality: 0.7,
complexity: 0.5,
maintainability: 0.7,
testability: 0.8,
performance: 0.7,
security: 0.6,
accessibility: 0.5,
usability: 0.6,
reliability: 0.7,
scalability: 0.6,
portability: 0.7,
reusability: 0.6,
readability: 0.8,
documentation: 0.5,
errorHandling: 0.6,
logging: 0.5,
monitoring: 0.4,
debugging: 0.6,
profiling: 0.4
};
}
initializeBuiltInRules() {
// Code Quality Rules
this.addRule({
id: 'large-file',
name: 'Large File Detection',
description: 'Detect files that are too large and suggest splitting',
category: 'code-quality',
subcategory: 'file-size',
severity: 'medium',
priority: 3,
conditions: [
{ type: 'file-size', operator: 'greater-than', value: 1000, weight: 1.0 }
],
actions: [
{ type: 'suggest', message: 'Consider splitting this large file into smaller modules', code: '// Split into smaller modules', confidence: 0.8, effort: 'high', impact: 'high', risk: 'medium' }
],
metadata: this.createSuggestionMetadata()
});
this.addRule({
id: 'high-complexity',
name: 'High Complexity Detection',
description: 'Detect files with high cyclomatic complexity',
category: 'code-quality',
subcategory: 'complexity',
severity: 'high',
priority: 4,
conditions: [
{ type: 'file-complexity', operator: 'greater-than', value: 10, weight: 1.0 }
],
actions: [
{ type: 'suggest', message: 'Reduce cyclomatic complexity by breaking down complex functions', code: '// Break down complex functions', confidence: 0.9, effort: 'medium', impact: 'high', risk: 'low' }
],
metadata: this.createSuggestionMetadata()
});
// Performance Rules
this.addRule({
id: 'performance-optimization',
name: 'Performance Optimization',
description: 'Suggest performance optimizations',
category: 'performance',
subcategory: 'optimization',
severity: 'medium',
priority: 3,
conditions: [
{ type: 'file-performance', operator: 'less-than', value: 0.7, weight: 1.0 }
],
actions: [
{ type: 'optimize', message: 'Optimize performance by reducing unnecessary computations', code: '// Performance optimization', confidence: 0.7, effort: 'medium', impact: 'medium', risk: 'low' }
],
metadata: this.createSuggestionMetadata()
});
// Security Rules
this.addRule({
id: 'security-vulnerability',
name: 'Security Vulnerability Detection',
description: 'Detect potential security vulnerabilities',
category: 'security',
subcategory: 'vulnerability',
severity: 'critical',
priority: 5,
conditions: [
{ type: 'file-security', operator: 'less-than', value: 0.6, weight: 1.0 }
],
actions: [
{ type: 'warn', message: 'Address potential security vulnerabilities', code: '// Security fix', confidence: 0.9, effort: 'high', impact: 'high', risk: 'high' }
],
metadata: this.createSuggestionMetadata()
});
// Maintainability Rules
this.addRule({
id: 'low-maintainability',
name: 'Low Maintainability Detection',
description: 'Detect files with low maintainability',
category: 'maintainability',
subcategory: 'maintainability',
severity: 'medium',
priority: 3,
conditions: [
{ type: 'file-maintainability', operator: 'less-than', value: 0.6, weight: 1.0 }
],
actions: [
{ type: 'refactor', message: 'Improve maintainability by reducing coupling and improving cohesion', code: '// Improve maintainability', confidence: 0.8, effort: 'high', impact: 'medium', risk: 'medium' }
],
metadata: this.createSuggestionMetadata()
});
// Testability Rules
this.addRule({
id: 'low-testability',
name: 'Low Testability Detection',
description: 'Detect files with low testability',
category: 'testability',
subcategory: 'testability',
severity: 'medium',
priority: 3,
conditions: [
{ type: 'file-testability', operator: 'less-than', value: 0.6, weight: 1.0 }
],
actions: [
{ type: 'test', message: 'Improve testability by reducing dependencies and improving modularity', code: '// Improve testability', confidence: 0.8, effort: 'medium', impact: 'medium', risk: 'low' }
],
metadata: this.createSuggestionMetadata()
});
// Cross-File Dependency Rules
this.addRule({
id: 'circular-dependency',
name: 'Circular Dependency Detection',
description: 'Detect circular dependencies between files',
category: 'architecture',
subcategory: 'circular-dependency',
severity: 'high',
priority: 4,
conditions: [
{ type: 'circular-dependency', operator: 'exists', value: true, weight: 1.0 }
],
actions: [
{ type: 'decouple', message: 'Break circular dependency by introducing interfaces or abstractions', code: '// Break circular dependency', confidence: 0.9, effort: 'high', impact: 'high', risk: 'medium' }
],
metadata: this.createSuggestionMetadata()
});
this.addRule({
id: 'high-coupling',
name: 'High Coupling Detection',
description: 'Detect high coupling between files',
category: 'architecture',
subcategory: 'coupling',
severity: 'medium',
priority: 3,
conditions: [
{ type: 'cluster-coupling', operator: 'greater-than', value: 0.7, weight: 1.0 }
],
actions: [
{ type: 'modularize', message: 'Reduce coupling by improving modularity', code: '// Reduce coupling', confidence: 0.8, effort: 'high', impact: 'medium', risk: 'medium' }
],
metadata: this.createSuggestionMetadata()
});
// Visual Testing Rules
this.addRule({
id: 'visual-testing-migration',
name: 'Visual Testing Migration',
description: 'Suggest migration from other visual testing platforms to SmartUI',
category: 'visual-testing',
subcategory: 'migration',
severity: 'medium',
priority: 3,
conditions: [
{ type: 'pattern-match', operator: 'matches', value: /percy|applitools|sauce/i, weight: 1.0 }
],
actions: [
{ type: 'migrate', message: 'Migrate to SmartUI for better performance and pricing', code: 'smartui.snapshot($1)', confidence: 0.9, effort: 'low', impact: 'high', risk: 'low' }
],
metadata: this.createSuggestionMetadata()
});
// Documentation Rules
this.addRule({
id: 'missing-documentation',
name: 'Missing Documentation Detection',
description: 'Detect files with missing or insufficient documentation',
category: 'documentation',
subcategory: 'documentation',
severity: 'low',
priority: 2,
conditions: [
{ type: 'file-documentation', operator: 'less-than', value: 0.5, weight: 1.0 }
],
actions: [
{ type: 'document', message: 'Add comprehensive documentation to improve code understanding', code: '// Add documentation', confidence: 0.7, effort: 'medium', impact: 'low', risk: 'low' }
],
metadata: this.createSuggestionMetadata()
});
}
analyze(files, context, patterns, semantics, crossFile) {
const startTime = Date.now();
const startMemory = process.memoryUsage().heapUsed;
const suggestions = [];
const recommendations = [];
const transformations = [];
// Apply all rules
this.rules.forEach((rule, ruleId) => {
const ruleSuggestions = this.applyRule(rule, files, context, patterns, semantics, crossFile);
suggestions.push(...ruleSuggestions);
});
// Generate recommendations
recommendations.push(...this.generateRecommendations(suggestions, files, context, patterns, semantics, crossFile));
// Generate transformations
transformations.push(...this.generateTransformations(suggestions, files, context, patterns, semantics, crossFile));
// Update statistics
this.updateStatistics(suggestions);
const endTime = Date.now();
const endMemory = process.memoryUsage().heapUsed;
this.statistics.processingTime = endTime - startTime;
this.statistics.memoryUsage = endMemory - startMemory;
return {
suggestions,
statistics: this.statistics,
recommendations,
transformations,
metadata: this.metadata
};
}
applyRule(rule, files, context, patterns, semantics, crossFile) {
const suggestions = [];
for (const [filePath, ast] of files) {
if (this.checkConditions(rule.conditions, filePath, ast, context, patterns, semantics, crossFile)) {
const suggestion = this.createSuggestion(rule, filePath, ast, context, patterns, semantics, crossFile);
suggestions.push(suggestion);
}
}
return suggestions;
}
checkConditions(conditions, filePath, ast, context, patterns, semantics, crossFile) {
let totalWeight = 0;
let matchedWeight = 0;
for (const condition of conditions) {
totalWeight += condition.weight;
if (this.evaluateCondition(condition, filePath, ast, context, patterns, semantics, crossFile)) {
matchedWeight += condition.weight;
}
}
return totalWeight > 0 && matchedWeight / totalWeight >= 0.5;
}
evaluateCondition(condition, filePath, ast, context, patterns, semantics, crossFile) {
switch (condition.type) {
case 'file-size':
return this.compareValues(ast.raw.length, condition.operator, condition.value);
case 'file-complexity':
return this.compareValues(this.calculateComplexity(ast), condition.operator, condition.value);
case 'file-maintainability':
return this.compareValues(context.maintainability.overall === 'excellent' ? 0.9 : context.maintainability.overall === 'good' ? 0.7 : context.maintainability.overall === 'fair' ? 0.5 : 0.3, condition.operator, condition.value);
case 'file-testability':
return this.compareValues(context.testability.overall === 'excellent' ? 0.9 : context.testability.overall === 'good' ? 0.7 : context.testability.overall === 'fair' ? 0.5 : 0.3, condition.operator, condition.value);
case 'file-performance':
return this.compareValues(0.7, condition.operator, condition.value);
case 'file-security':
return this.compareValues(0.6, condition.operator, condition.value);
case 'file-documentation':
return this.compareValues(0.5, condition.operator, condition.value);
case 'circular-dependency':
return this.compareValues(crossFile.cycles.length > 0, condition.operator, condition.value);
case 'cluster-coupling':
return this.compareValues(crossFile.clusters.reduce((sum, cluster) => sum + cluster.coupling, 0) / crossFile.clusters.length, condition.operator, condition.value);
case 'pattern-match':
return this.compareValues(ast.raw, condition.operator, condition.value);
default:
return false;
}
}
compareValues(actual, operator, expected) {
switch (operator) {
case 'equals':
return actual === expected;
case 'not-equals':
return actual !== expected;
case 'contains':
return String(actual).includes(String(expected));
case 'not-contains':
return !String(actual).includes(String(expected));
case 'matches':
return new RegExp(expected).test(String(actual));
case 'not-matches':
return !new RegExp(expected).test(String(actual));
case 'greater-than':
return Number(actual) > Number(expected);
case 'less-than':
return Number(actual) < Number(expected);
case 'greater-equals':
return Number(actual) >= Number(expected);
case 'less-equals':
return Number(actual) <= Number(expected);
case 'exists':
return actual != null;
case 'not-exists':
return actual == null;
case 'in':
return Array.isArray(expected) && expected.includes(actual);
case 'not-in':
return Array.isArray(expected) && !expected.includes(actual);
case 'between':
return Array.isArray(expected) && expected.length === 2 && Number(actual) >= Number(expected[0]) && Number(actual) <= Number(expected[1]);
case 'not-between':
return Array.isArray(expected) && expected.length === 2 && (Number(actual) < Number(expected[0]) || Number(actual) > Number(expected[1]));
case 'starts-with':
return String(actual).startsWith(String(expected));
case 'ends-with':
return String(actual).endsWith(String(expected));
case 'regex':
return new RegExp(expected).test(String(actual));
case 'not-regex':
return !new RegExp(expected).test(String(actual));
default:
return false;
}
}
createSuggestion(rule, filePath, ast, context, patterns, semantics, crossFile) {
const action = rule.actions[0];
return {
id: `${rule.id}_${filePath}_${Date.now()}`,
type: action.type,
title: action.message,
description: action.message,
priority: this.calculatePriority(rule, action),
effort: action.effort,
impact: action.impact,
risk: action.risk,
confidence: action.confidence,
category: rule.category,
subcategory: rule.subcategory,
severity: rule.severity,
urgency: this.calculateUrgency(rule, action),
files: [filePath],
dependencies: [],
prerequisites: [],
alternatives: [],
resources: [],
examples: [],
code: action.code,
validation: this.generateValidation(action.code),
rollback: this.generateRollback(action.code),
metadata: this.createSuggestionMetadata()
};
}
calculatePriority(rule, action) {
if (rule.severity === 'critical')
return 'critical';
if (rule.severity === 'high')
return 'high';
if (rule.severity === 'medium')
return 'medium';
return 'low';
}
calculateUrgency(rule, action) {
if (action.impact === 'high' && action.risk === 'low')
return 'high';
if (action.impact === 'high' && action.risk === 'medium')
return 'medium';
if (action.impact === 'medium' && action.risk === 'low')
return 'medium';
return 'low';
}
generateValidation(code) {
return `// Validation: Check if suggestion was applied correctly`;
}
generateRollback(code) {
return `// Rollback: Revert suggestion if needed`;
}
generateRecommendations(suggestions, files, context, patterns, semantics, crossFile) {
const recommendations = [];
// Group suggestions by category
const suggestionsByCategory = this.groupSuggestionsByCategory(suggestions);
// Generate recommendations for each category
for (const [category, categorySuggestions] of suggestionsByCategory) {
const recommendation = this.createRecommendation(category, categorySuggestions, files, context, patterns, semantics, crossFile);
if (recommendation) {
recommendations.push(recommendation);
}
}
return recommendations;
}
groupSuggestionsByCategory(suggestions) {
const grouped = new Map();
for (const suggestion of suggestions) {
const category = suggestion.category;
if (!grouped.has(category)) {
grouped.set(category, []);
}
grouped.get(category).push(suggestion);
}
return grouped;
}
createRecommendation(category, suggestions, files, context, patterns, semantics, crossFile) {
if (suggestions.length === 0)
return null;
const firstSuggestion = suggestions[0];
const priority = this.calculateRecommendationPriority(suggestions);
const effort = this.calculateRecommendationEffort(suggestions);
const impact = this.calculateRecommendationImpact(suggestions);
const risk = this.calculateRecommendationRisk(suggestions);
return {
id: `rec_${category}_${Date.now()}`,
type: category,
title: `${category.charAt(0).toUpperCase() + category.slice(1)} Optimization`,
description: `Found ${suggestions.length} ${category} suggestions that can be optimized`,
priority,
effort,
impact,
risk,
confidence: this.calculateRecommendationConfidence(suggestions),
evidence: suggestions.map(s => s.title),
alternatives: [],
prerequisites: [],
dependencies: [],
resources: [],
examples: [],
code: `// ${category} optimization code`,
validation: `// Validation for ${category} optimization`,
rollback: `// Rollback for ${category} optimization`,
metadata: this.createSuggestionMetadata()
};
}
calculateRecommendationPriority(suggestions) {
const criticalCount = suggestions.filter(s => s.priority === 'critical').length;
const highCount = suggestions.filter(s => s.priority === 'high').length;
if (criticalCount > 0)
return 'critical';
if (highCount > 2)
return 'high';
return 'medium';
}
calculateRecommendationEffort(suggestions) {
const highEffortCount = suggestions.filter(s => s.effort === 'high').length;
const mediumEffortCount = suggestions.filter(s => s.effort === 'medium').length;
if (highEffortCount > 2)
return 'high';
if (mediumEffortCount > 3)
return 'medium';
return 'low';
}
calculateRecommendationImpact(suggestions) {
const highImpactCount = suggestions.filter(s => s.impact === 'high').length;
const mediumImpactCount = suggestions.filter(s => s.impact === 'medium').length;
if (highImpactCount > 1)
return 'high';
if (mediumImpactCount > 2)
return 'medium';
return 'low';
}
calculateRecommendationRisk(suggestions) {
const highRiskCount = suggestions.filter(s => s.risk === 'high').length;
const mediumRiskCount = suggestions.filter(s => s.risk === 'medium').length;
if (highRiskCount > 0)
return 'high';
if (mediumRiskCount > 2)
return 'medium';
return 'low';
}
calculateRecommendationConfidence(suggestions) {
const totalConfidence = suggestions.reduce((sum, s) => sum + s.confidence, 0);
return totalConfidence / suggestions.length;
}
generateTransformations(suggestions, files, context, patterns, semantics, crossFile) {
const transformations = [];
for (const suggestion of suggestions) {
if (suggestion.type === 'migrate' || suggestion.type === 'refactor' || suggestion.type === 'optimize') {
transformations.push({
id: `transform_${suggestion.id}`,
name: suggestion.title,
description: suggestion.description,
type: suggestion.type,
from: suggestion.files[0],
to: suggestion.code,
confidence: suggestion.confidence,
effort: suggestion.effort,
impact: suggestion.impact,
risk: suggestion.risk,
files: suggestion.files,
code: suggestion.code,
validation: suggestion.validation,
rollback: suggestion.rollback,
metadata: suggestion.metadata
});
}
}
return transformations;
}
updateStatistics(suggestions) {
this.statistics.totalSuggestions = suggestions.length;
for (const suggestion of suggestions) {
if (suggestion.priority === 'critical')
this.statistics.criticalSuggestions++;
else if (suggestion.priority === 'high')
this.statistics.highPrioritySuggestions++;
else if (suggestion.priority === 'medium')
this.statistics.mediumPrioritySuggestions++;
else
this.statistics.lowPrioritySuggestions++;
if (suggestion.effort === 'high')
this.statistics.highEffortSuggestions++;
else if (suggestion.effort === 'medium')
this.statistics.mediumEffortSuggestions++;
else
this.statistics.lowEffortSuggestions++;
if (suggestion.impact === 'high')
this.statistics.highImpactSuggestions++;
else if (suggestion.impact === 'medium')
this.statistics.mediumImpactSuggestions++;
else
this.statistics.lowImpactSuggestions++;
if (suggestion.risk === 'high')
this.statistics.highRiskSuggestions++;
else if (suggestion.risk === 'medium')
this.statistics.mediumRiskSuggestions++;
else
this.statistics.lowRiskSuggestions++;
if (suggestion.confidence >= 0.8)
this.statistics.highConfidenceSuggestions++;
else if (suggestion.confidence >= 0.6)
this.statistics.mediumConfidenceSuggestions++;
else
this.statistics.lowConfidenceSuggestions++;
}
this.statistics.averageConfidence = this.calculateAverageConfidence(suggestions);
this.statistics.averagePriority = this.calculateAveragePriority(suggestions);
this.statistics.averageEffort = this.calculateAverageEffort(suggestions);
this.statistics.averageImpact = this.calculateAverageImpact(suggestions);
this.statistics.averageRisk = this.calculateAverageRisk(suggestions);
}
calculateAverageConfidence(suggestions) {
if (suggestions.length === 0)
return 0;
const totalConfidence = suggestions.reduce((sum, s) => sum + s.confidence, 0);
return totalConfidence / suggestions.length;
}
calculateAveragePriority(suggestions) {
if (suggestions.length === 0)
return 0;
const priorityMap = { 'low': 1, 'medium': 2, 'high': 3, 'critical': 4 };
const totalPriority = suggestions.reduce((sum, s) => sum + priorityMap[s.priority], 0);
return totalPriority / suggestions.length;
}
calculateAverageEffort(suggestions) {
if (suggestions.length === 0)
return 0;
const effortMap = { 'low': 1, 'medium': 2, 'high': 3 };
const totalEffort = suggestions.reduce((sum, s) => sum + effortMap[s.effort], 0);
return totalEffort / suggestions.length;
}
calculateAverageImpact(suggestions) {
if (suggestions.length === 0)
return 0;
const impactMap = { 'low': 1, 'medium': 2, 'high': 3 };
const totalImpact = suggestions.reduce((sum, s) => sum + impactMap[s.impact], 0);
return totalImpact / suggestions.length;
}
calculateAverageRisk(suggestions) {
if (suggestions.length === 0)
return 0;
const riskMap = { 'low': 1, 'medium': 2, 'high': 3 };
const totalRisk = suggestions.reduce((sum, s) => sum + riskMap[s.risk], 0);
return totalRisk / suggestions.length;
}
calculateComplexity(ast) {
return 1.0;
}
createSuggestionMetadata() {
return {
language: 'javascript',
framework: null,
platform: null,
version: '1.0.0',
timestamp: new Date().toISOString(),
processingTime: 0,
memoryUsage: 0,
confidence: 0.8,
quality: 0.7,
complexity: 0.5,
maintainability: 0.7,
testability: 0.8,
performance: 0.7,
security: 0.6,
accessibility: 0.5,
usability: 0.6,
reliability: 0.7,
scalability: 0.6,
portability: 0.7,
reusability: 0.6,
readability: 0.8,
documentation: 0.5,
errorHandling: 0.6,
logging: 0.5,
monitoring: 0.4,
debugging: 0.6,
profiling: 0.4
};
}
// Public methods
addRule(rule) {
this.rules.set(rule.id, rule);
}
removeRule(ruleId) {
this.rules.delete(ruleId);
}
getRule(ruleId) {
return this.rules.get(ruleId);
}
getAllRules() {
return Array.from(this.rules.values());
}
getStatistics() {
return { ...this.statistics };
}
getMetadata() {
return { ...this.metadata };
}
}
exports.IntelligentSuggestionEngine = IntelligentSuggestionEngine;
//# sourceMappingURL=IntelligentSuggestionEngine.js.map