UNPKG

optivise

Version:

Optivise - The Ultimate Optimizely Development Assistant with AI-powered features, zero-config setup, and comprehensive development support

592 lines (590 loc) 28.4 kB
/** * Code Analyzer Tool (optidev_code_analyzer) * Real-time code analysis for performance, security, and best practices */ import { ProductDetectionService } from '../services/product-detection-service.js'; import { RuleIntelligenceService } from '../services/rule-intelligence-service.js'; import { RequestFormatter } from '../formatters/request-formatter.js'; import { FormatterTemplates } from '../formatters/templates.js'; import { z } from 'zod'; export const CodeAnalyzerRequestSchema = z.object({ codeSnippet: z.string().min(1, 'codeSnippet is required'), language: z.enum(['typescript', 'javascript', 'csharp']).default('typescript'), analysisType: z.enum(['performance', 'security', 'best-practices', 'all']).default('all'), userPrompt: z.string().optional(), promptContext: z.any().optional(), projectPath: z.string().optional() }); export class CodeAnalyzerTool { productDetection; logger; ruleService; // Language-specific analysis patterns static LANGUAGE_PATTERNS = { typescript: { performance: [ { pattern: /\.map\(.*\)\.filter\(.*\)/g, message: 'Chained map+filter can be optimized', severity: 'warning' }, { pattern: /for\s*\(\s*let\s+\w+\s*=\s*0[^}]*{[^}]*}/g, message: 'Consider using forEach or map for better readability', severity: 'info' }, { pattern: /JSON\.parse\(JSON\.stringify\(/g, message: 'Deep cloning with JSON is inefficient', severity: 'warning' }, { pattern: /new\s+Date\(\)/g, message: 'Creating dates in loops can be expensive', severity: 'info' } ], security: [ { pattern: /eval\s*\(/g, message: 'eval() is dangerous and should be avoided', severity: 'critical' }, { pattern: /innerHTML\s*=/g, message: 'innerHTML can lead to XSS vulnerabilities', severity: 'error' }, { pattern: /localStorage\./g, message: 'localStorage may expose sensitive data', severity: 'warning' }, { pattern: /password.*=.*['"]/g, message: 'Hardcoded passwords are security risks', severity: 'critical' } ], bestPractices: [ { pattern: /var\s+/g, message: 'Use let or const instead of var', severity: 'warning' }, { pattern: /==\s*[^=]/g, message: 'Use strict equality (===) instead of ==', severity: 'warning' }, { pattern: /console\.log/g, message: 'Remove console.log in production code', severity: 'info' }, { pattern: /function\s+\w+\s*\([^)]*\)\s*{[^}]*}/g, message: 'Consider using arrow functions for consistency', severity: 'info' } ] }, csharp: { performance: [ { pattern: /string\s+\w+\s*=\s*[^+]*\+[^;]*;/g, message: 'Use StringBuilder for string concatenation', severity: 'warning' }, { pattern: /foreach\s*\([^)]*\)\s*{[^}]*\.Add\(/g, message: 'Consider using LINQ methods instead of foreach+Add', severity: 'info' }, { pattern: /new\s+List<[^>]*>\(\)/g, message: 'Consider specifying initial capacity for List', severity: 'info' }, { pattern: /\.ToList\(\)\.Count/g, message: 'Use .Count() instead of .ToList().Count', severity: 'warning' } ], security: [ { pattern: /SqlCommand\([^)]*\+/g, message: 'SQL injection vulnerability - use parameterized queries', severity: 'critical' }, { pattern: /Password\s*=\s*['"]\w+['"]/g, message: 'Hardcoded passwords in connection strings', severity: 'critical' }, { pattern: /Random\s*\(\)/g, message: 'Use cryptographically secure random for security purposes', severity: 'warning' }, { pattern: /catch\s*\([^)]*\)\s*{\s*}/g, message: 'Empty catch blocks hide exceptions', severity: 'error' } ], bestPractices: [ { pattern: /public\s+(?!class|interface)[^{]*{[^}]*public\s+/g, message: 'Consider encapsulation - avoid too many public members', severity: 'info' }, { pattern: /if\s*\([^)]*==\s*null\)\s*return/g, message: 'Consider using null-conditional operator (?)', severity: 'info' }, { pattern: /async\s+void/g, message: 'Avoid async void except for event handlers', severity: 'warning' }, { pattern: /ConfigureAwait\(false\)/g, message: 'Good practice: ConfigureAwait(false) in library code', severity: 'info' } ] }, javascript: { performance: [ { pattern: /document\.getElementById.*getElementById/g, message: 'Cache DOM queries instead of repeated lookups', severity: 'warning' }, { pattern: /for\s*\([^)]*\.length[^)]*\)/g, message: 'Cache array length in for loops', severity: 'info' }, { pattern: /\+\s*new\s+Date/g, message: 'Use Date.now() instead of +new Date()', severity: 'info' } ], security: [ { pattern: /eval\s*\(/g, message: 'eval() is dangerous and should be avoided', severity: 'critical' }, { pattern: /innerHTML\s*=/g, message: 'innerHTML can lead to XSS vulnerabilities', severity: 'error' }, { pattern: /document\.write/g, message: 'document.write can be exploited for XSS', severity: 'error' } ], bestPractices: [ { pattern: /var\s+/g, message: 'Use let or const instead of var', severity: 'warning' }, { pattern: /==\s*[^=]/g, message: 'Use strict equality (===) instead of ==', severity: 'warning' }, { pattern: /function\s*\(/g, message: 'Consider using arrow functions', severity: 'info' } ] } }; // Optimizely-specific patterns static OPTIMIZELY_PATTERNS = { commerce: { performance: [ { pattern: /GetChildren<.*>\(\)\.Where/g, message: 'Consider using filtered GetChildren overload', severity: 'warning' }, { pattern: /\.Current\.(?!Store)/g, message: 'Avoid excessive use of .Current properties', severity: 'info' } ], bestPractices: [ { pattern: /ServiceLocator\.Current/g, message: 'Use dependency injection instead of ServiceLocator', severity: 'warning' }, { pattern: /\[Serializable\]/g, message: 'Consider if Serializable is needed for performance', severity: 'info' } ] }, cms: { performance: [ { pattern: /DataFactory\.Instance/g, message: 'Use IContentRepository with dependency injection', severity: 'warning' }, { pattern: /GetChildren\(\)\.Count\(\)/g, message: 'Use GetChildren().Count() extension for better performance', severity: 'info' } ], bestPractices: [ { pattern: /PageData/g, message: 'Consider using strongly-typed page models', severity: 'info' }, { pattern: /\[Display.*Name\s*=\s*"[^"]*"\]/g, message: 'Good practice: Using Display attributes for editor labels', severity: 'info' } ] } }; constructor(logger) { this.logger = logger; this.productDetection = new ProductDetectionService(logger); this.ruleService = new RuleIntelligenceService(logger); } async initialize() { await this.productDetection.initialize(); await this.ruleService.initialize(); this.logger.info('Code Analyzer Tool initialized'); } /** * Analyze code snippet for various quality aspects */ async analyzeCode(request) { try { const parsed = CodeAnalyzerRequestSchema.safeParse(request); if (!parsed.success) { throw parsed.error; } this.logger.info('Analyzing code snippet', { language: request.language, analysisType: request.analysisType, codeLength: request.codeSnippet.length }); // 1. Detect Optimizely products in code (and capture evidence) const detection = await this.productDetection.detectFromPrompt(request.codeSnippet); const detectedProducts = detection.products; // 2. Perform requested analysis const analysisResults = {}; if (request.analysisType === 'performance' || request.analysisType === 'all') { analysisResults.performance = this.analyzePerformance(request, detectedProducts); } if (request.analysisType === 'security' || request.analysisType === 'all') { analysisResults.security = this.analyzeSecurity(request, detectedProducts); } if (request.analysisType === 'best-practices' || request.analysisType === 'all') { analysisResults.bestPractices = this.analyzeBestPractices(request, detectedProducts); } // 3. Calculate overall quality const overallQuality = this.calculateOverallQuality(analysisResults); // 4. Identify refactoring opportunities const refactoringOpportunities = this.identifyRefactoringOpportunities(request, detectedProducts, analysisResults); const base = { detectedProducts, language: request.language, analysisType: request.analysisType, ...analysisResults, overallQuality, refactoringOpportunities }; // Build formatter blocks const blocks = []; blocks.push({ type: 'analysis', title: 'Code Quality Summary', content: base.overallQuality.summary, relevance: 0.95 }); // Always include a detection evidence block for downstream consumers/tests blocks.push({ type: 'detection-evidence', title: 'Product Detection Evidence', content: JSON.stringify(detection?.evidence?.slice(0, 20) || []), source: 'product-detection', relevance: 0.6 }); // Optional: include rule intelligence if projectPath provided if (request.projectPath) { try { const ruleAnalysis = await this.ruleService.analyzeIDERules(request.projectPath); blocks.push({ type: 'rules', title: 'IDE Rules Summary', content: JSON.stringify({ files: ruleAnalysis.foundFiles, lintWarnings: ruleAnalysis.lintWarnings, conflicts: ruleAnalysis.conflicts, normalized: ruleAnalysis.normalizedDirectives?.slice(0, 20), proposed: ruleAnalysis.proposedCursorRules?.slice(0, 2000), diff: ruleAnalysis.proposedCursorRulesDiff?.slice(0, 2000) }), source: request.projectPath }); } catch { } } if (analysisResults.performance) { blocks.push({ type: 'analysis', title: 'Performance Issues', content: JSON.stringify(analysisResults.performance.issues).slice(0, 4000), relevance: 0.85 }); } if (analysisResults.security) { blocks.push({ type: 'analysis', title: 'Security Findings', content: JSON.stringify(analysisResults.security.vulnerabilities).slice(0, 4000), relevance: 0.9 }); } if (analysisResults.bestPractices) { blocks.push({ type: 'analysis', title: 'Best Practices Violations', content: JSON.stringify(analysisResults.bestPractices.violations).slice(0, 4000), relevance: 0.7 }); } // Attach formatted LLM request (handoff to IDE agent) const llm_request = RequestFormatter.format({ toolName: 'optidev_code_analyzer', userPrompt: request.userPrompt, promptContext: request.promptContext, summary: 'Analyze the provided code issues and propose improvements with examples.', products: detectedProducts, blocks, template: FormatterTemplates.optidev_code_analyzer }); base.llm_request = llm_request; return base; } catch (error) { this.logger.error('Failed to analyze code', error); throw error; } } /** * Detect Optimizely products in code */ async detectProductsInCode(codeSnippet) { const detection = await this.productDetection.detectFromPrompt(codeSnippet); return detection.products; } /** * Analyze code performance */ analyzePerformance(request, products) { const issues = []; const optimizations = []; // Apply language-specific patterns const languagePatterns = CodeAnalyzerTool.LANGUAGE_PATTERNS[request.language]; if (languagePatterns?.performance) { issues.push(...this.findPatternMatches(request.codeSnippet, languagePatterns.performance, 'performance')); } // Apply Optimizely-specific patterns products.forEach(product => { const productPatterns = CodeAnalyzerTool.OPTIMIZELY_PATTERNS[product]; if (productPatterns?.performance) { issues.push(...this.findPatternMatches(request.codeSnippet, productPatterns.performance, 'performance')); } }); // Generate optimizations based on issues found issues.forEach(issue => { optimizations.push(this.generateOptimization(issue, request.language)); }); // Calculate performance score const overallScore = Math.max(0, 100 - (issues.length * 10) - (issues.filter(i => i.severity === 'error').length * 20)); return { overallScore, issues, optimizations }; } /** * Analyze code security */ analyzeSecurity(request, products) { const vulnerabilities = []; // Apply language-specific security patterns const languagePatterns = CodeAnalyzerTool.LANGUAGE_PATTERNS[request.language]; if (languagePatterns?.security) { vulnerabilities.push(...this.findPatternMatches(request.codeSnippet, languagePatterns.security, 'security')); } // Determine risk level const criticalIssues = vulnerabilities.filter(v => v.severity === 'critical').length; const errorIssues = vulnerabilities.filter(v => v.severity === 'error').length; let riskLevel = 'low'; if (criticalIssues > 0) riskLevel = 'critical'; else if (errorIssues > 0) riskLevel = 'high'; else if (vulnerabilities.length > 0) riskLevel = 'medium'; // Generate recommendations const recommendations = this.generateSecurityRecommendations(vulnerabilities, request.language); return { riskLevel, vulnerabilities, recommendations }; } /** * Analyze best practices compliance */ analyzeBestPractices(request, products) { const violations = []; // Apply language-specific best practice patterns const languagePatterns = CodeAnalyzerTool.LANGUAGE_PATTERNS[request.language]; if (languagePatterns?.bestPractices) { violations.push(...this.findPatternMatches(request.codeSnippet, languagePatterns.bestPractices, 'best-practices')); } // Apply Optimizely-specific patterns products.forEach(product => { const productPatterns = CodeAnalyzerTool.OPTIMIZELY_PATTERNS[product]; if (productPatterns?.bestPractices) { violations.push(...this.findPatternMatches(request.codeSnippet, productPatterns.bestPractices, 'best-practices')); } }); // Calculate compliance score const complianceScore = Math.max(0, 100 - (violations.length * 8) - (violations.filter(v => v.severity === 'error').length * 15)); // Generate suggestions const suggestions = this.generateBestPracticeSuggestions(violations, request.language, products); return { complianceScore, violations, suggestions }; } /** * Find pattern matches in code */ findPatternMatches(code, patterns, category) { const issues = []; const lines = code.split('\n'); patterns.forEach(({ pattern, message, severity }) => { lines.forEach((line, lineIndex) => { const matches = line.match(pattern); if (matches) { issues.push({ line: lineIndex + 1, column: line.indexOf(matches[0]) + 1, severity, category, message, suggestion: this.generateSuggestion(message, category) }); } }); }); return issues; } /** * Generate suggestion based on issue */ generateSuggestion(message, category) { const suggestionMap = { 'Use let or const instead of var': 'Replace "var" with "let" for block-scoped variables or "const" for constants', 'Use strict equality (===) instead of ==': 'Replace "==" with "===" to avoid type coercion issues', 'eval() is dangerous and should be avoided': 'Use safer alternatives like JSON.parse() or Function constructor', 'innerHTML can lead to XSS vulnerabilities': 'Use textContent for text or sanitize HTML content before assignment', 'Use StringBuilder for string concatenation': 'Replace string concatenation with StringBuilder.Append() for better performance' }; return suggestionMap[message] || `Consider reviewing this ${category} issue and applying appropriate fixes`; } /** * Generate optimization recommendation */ generateOptimization(issue, language) { const optimizationMap = { 'Chained map+filter can be optimized': { title: 'Combine map and filter operations', description: 'Use a single reduce or flatMap operation instead of chaining map and filter', impact: 'medium', effort: 'low', codeExample: ` // Instead of: items.map(x => transform(x)).filter(x => x.isValid) // Use: items.reduce((acc, x) => { const transformed = transform(x); return transformed.isValid ? [...acc, transformed] : acc; }, [])` }, 'Cache DOM queries instead of repeated lookups': { title: 'Cache DOM element references', description: 'Store frequently accessed DOM elements in variables', impact: 'high', effort: 'low', codeExample: ` // Instead of: document.getElementById('myElement').style.color = 'red'; document.getElementById('myElement').style.fontSize = '14px'; // Use: const element = document.getElementById('myElement'); element.style.color = 'red'; element.style.fontSize = '14px';` } }; return optimizationMap[issue.message] || { title: 'General optimization opportunity', description: issue.message, impact: 'medium', effort: 'medium' }; } /** * Generate security recommendations */ generateSecurityRecommendations(vulnerabilities, language) { const recommendations = [ 'Implement input validation and sanitization', 'Use parameterized queries to prevent SQL injection', 'Avoid storing sensitive data in client-side code', 'Implement proper error handling without exposing system details' ]; // Add specific recommendations based on vulnerabilities found if (vulnerabilities.some(v => v.message.includes('eval'))) { recommendations.push('Replace eval() with safer alternatives like JSON.parse()'); } if (vulnerabilities.some(v => v.message.includes('innerHTML'))) { recommendations.push('Use textContent or sanitize HTML before using innerHTML'); } if (vulnerabilities.some(v => v.message.includes('password'))) { recommendations.push('Move sensitive credentials to secure configuration files'); } return recommendations; } /** * Generate best practice suggestions */ generateBestPracticeSuggestions(violations, language, products) { const suggestions = []; // Language-specific suggestions if (language === 'typescript' || language === 'javascript') { suggestions.push({ category: 'Modern JavaScript/TypeScript', description: 'Use modern ES6+ features for cleaner, more maintainable code', examples: [ 'Use const and let instead of var', 'Use arrow functions for shorter syntax', 'Use template literals instead of string concatenation', 'Use destructuring for object and array operations' ] }); } if (language === 'csharp') { suggestions.push({ category: 'C# Best Practices', description: 'Follow C# coding standards and modern language features', examples: [ 'Use dependency injection instead of service locator pattern', 'Implement proper async/await patterns', 'Use null-conditional operators for safer null checking', 'Follow proper exception handling practices' ] }); } // Optimizely-specific suggestions if (products.includes('commerce')) { suggestions.push({ category: 'Optimizely Commerce', description: 'Follow Commerce development best practices', examples: [ 'Use dependency injection for service access', 'Implement proper caching strategies for catalog data', 'Use strongly-typed models for business objects', 'Follow extension development patterns' ] }); } if (products.includes('cms-paas') || products.includes('cms-saas')) { suggestions.push({ category: 'Optimizely CMS', description: 'Follow CMS development best practices', examples: [ 'Use IContentRepository instead of DataFactory', 'Implement proper content type definitions', 'Use strongly-typed page and block models', 'Follow template and component organization patterns' ] }); } return suggestions; } /** * Calculate overall code quality */ calculateOverallQuality(analysisResults) { let totalScore = 0; let scoreCount = 0; if (analysisResults.performance) { totalScore += analysisResults.performance.overallScore; scoreCount++; } if (analysisResults.security) { // Convert risk level to score const securityScore = { critical: 20, high: 40, medium: 70, low: 90 }[analysisResults.security.riskLevel]; totalScore += securityScore; scoreCount++; } if (analysisResults.bestPractices) { totalScore += analysisResults.bestPractices.complianceScore; scoreCount++; } const averageScore = scoreCount > 0 ? Math.round(totalScore / scoreCount) : 50; // Determine grade let grade; if (averageScore >= 90) grade = 'A'; else if (averageScore >= 80) grade = 'B'; else if (averageScore >= 70) grade = 'C'; else if (averageScore >= 60) grade = 'D'; else grade = 'F'; // Generate summary const summary = this.generateQualitySummary(averageScore, grade, analysisResults); return { score: averageScore, grade, summary }; } /** * Generate quality summary */ generateQualitySummary(score, grade, analysisResults) { const issueCount = (analysisResults.performance?.issues.length || 0) + (analysisResults.security?.vulnerabilities.length || 0) + (analysisResults.bestPractices?.violations.length || 0); if (score >= 90) { return `Excellent code quality (${score}/100). ${issueCount === 0 ? 'No issues found.' : `Only ${issueCount} minor issues to address.`}`; } else if (score >= 80) { return `Good code quality (${score}/100) with ${issueCount} issues to address.`; } else if (score >= 70) { return `Average code quality (${score}/100). Consider addressing ${issueCount} identified issues.`; } else if (score >= 60) { return `Below average code quality (${score}/100). ${issueCount} issues need attention.`; } else { return `Poor code quality (${score}/100). Significant improvements needed - ${issueCount} issues identified.`; } } /** * Identify refactoring opportunities */ identifyRefactoringOpportunities(request, products, analysisResults) { const opportunities = []; // Performance-based refactoring if (analysisResults.performance && analysisResults.performance.overallScore < 70) { opportunities.push({ type: 'Performance Optimization', description: 'Multiple performance issues detected that could benefit from refactoring', benefits: [ 'Improved application responsiveness', 'Reduced resource consumption', 'Better user experience' ], estimatedEffort: '2-4 hours' }); } // Security-based refactoring if (analysisResults.security && ['critical', 'high'].includes(analysisResults.security.riskLevel)) { opportunities.push({ type: 'Security Hardening', description: 'Security vulnerabilities require immediate refactoring', benefits: [ 'Improved application security', 'Reduced risk of attacks', 'Compliance with security standards' ], estimatedEffort: '4-8 hours' }); } // Best practices refactoring if (analysisResults.bestPractices && analysisResults.bestPractices.complianceScore < 70) { opportunities.push({ type: 'Code Modernization', description: 'Code doesn\'t follow modern best practices and could be improved', benefits: [ 'Improved code maintainability', 'Better team collaboration', 'Easier debugging and testing' ], estimatedEffort: '1-3 hours' }); } // Optimizely-specific refactoring if (products.length > 0) { opportunities.push({ type: 'Optimizely Integration Optimization', description: `Code could better leverage ${products.join(', ')} platform capabilities`, benefits: [ 'Better platform integration', 'Improved performance with platform features', 'More maintainable Optimizely code' ], estimatedEffort: '2-6 hours' }); } return opportunities; } } //# sourceMappingURL=code-analyzer-tool.js.map