Skip to main content
Glama
widgetAnalyzer.js6.47 kB
import { parseFlutterCode } from '../utils/parser.js'; import { getFlutterBestPractices } from '../validators/bestPractices.js'; import { getCacheManager } from '../cache/cacheManager.js'; const cache = getCacheManager(); export async function analyzeWidget(args) { const { widgetCode, checkAccessibility = true, checkPerformance = true } = args; // Check cache first const cacheKey = { widgetCode, checkAccessibility, checkPerformance }; const cachedResult = await cache.get('widgetAnalysis', cacheKey); if (cachedResult) { return cachedResult; } const issues = []; const suggestions = []; const metrics = { complexity: 0, nestingLevel: 0, widgetCount: 0, }; try { const widgetRegex = /(\w+)\s*\(/g; const widgets = [...widgetCode.matchAll(widgetRegex)].map(match => match[1]); metrics.widgetCount = widgets.length; const nestingLevel = calculateNestingLevel(widgetCode); metrics.nestingLevel = nestingLevel; if (checkAccessibility) { const accessibilityIssues = checkAccessibilityIssues(widgetCode); issues.push(...accessibilityIssues); } if (checkPerformance) { const performanceIssues = checkPerformanceIssues(widgetCode, widgets); issues.push(...performanceIssues); } const stateManagementIssues = checkStateManagement(widgetCode); issues.push(...stateManagementIssues); if (nestingLevel > 5) { suggestions.push({ type: 'refactoring', message: 'Consider extracting nested widgets into separate components', severity: 'medium', }); } if (!widgetCode.includes('const') && hasStaticWidgets(widgetCode)) { suggestions.push({ type: 'performance', message: 'Use const constructors for widgets that don\'t change', severity: 'low', }); } const result = { content: [ { type: 'text', text: JSON.stringify({ metrics, issues, suggestions, summary: generateSummary(metrics, issues, suggestions), }, null, 2), }, ], }; // Cache the result await cache.set('widgetAnalysis', cacheKey, result); return result; } catch (error) { return { content: [ { type: 'text', text: `Error analyzing widget: ${error.message}`, }, ], }; } } function calculateNestingLevel(code) { let maxLevel = 0; let currentLevel = 0; for (const char of code) { if (char === '(' || char === '{' || char === '[') { currentLevel++; maxLevel = Math.max(maxLevel, currentLevel); } else if (char === ')' || char === '}' || char === ']') { currentLevel--; } } return maxLevel; } function checkAccessibilityIssues(code) { const issues = []; if (code.includes('Image') && !code.includes('semanticLabel')) { issues.push({ type: 'accessibility', message: 'Images should have semanticLabel for screen readers', line: findLineNumber(code, 'Image'), severity: 'medium', }); } if (code.includes('IconButton') && !code.includes('tooltip')) { issues.push({ type: 'accessibility', message: 'IconButton should have a tooltip for better accessibility', line: findLineNumber(code, 'IconButton'), severity: 'medium', }); } if ((code.includes('GestureDetector') || code.includes('InkWell')) && !code.includes('Semantics')) { issues.push({ type: 'accessibility', message: 'Interactive widgets should be wrapped with Semantics for screen readers', severity: 'high', }); } return issues; } function checkPerformanceIssues(code, widgets) { const issues = []; if (code.includes('setState') && code.includes('build')) { const setStateCount = (code.match(/setState/g) || []).length; if (setStateCount > 3) { issues.push({ type: 'performance', message: 'Multiple setState calls detected. Consider using state management solution', severity: 'medium', }); } } if (code.includes('ListView') && !code.includes('ListView.builder')) { issues.push({ type: 'performance', message: 'Use ListView.builder for better performance with large lists', severity: 'medium', }); } const expensiveWidgets = ['BackdropFilter', 'Opacity', 'ClipPath']; expensiveWidgets.forEach(widget => { if (widgets.includes(widget)) { issues.push({ type: 'performance', message: `${widget} is computationally expensive. Use sparingly or consider alternatives`, severity: 'low', }); } }); return issues; } function checkStateManagement(code) { const issues = []; if (code.includes('StatefulWidget') && !code.includes('dispose')) { issues.push({ type: 'memory', message: 'StatefulWidget should implement dispose() to clean up resources', severity: 'high', }); } if (code.includes('StreamController') && !code.includes('.close()')) { issues.push({ type: 'memory', message: 'StreamController should be closed in dispose()', severity: 'high', }); } return issues; } function hasStaticWidgets(code) { const staticWidgets = ['Text', 'Icon', 'Container', 'Padding', 'Center']; return staticWidgets.some(widget => code.includes(widget)); } function findLineNumber(code, searchTerm) { const lines = code.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].includes(searchTerm)) { return i + 1; } } return null; } function generateSummary(metrics, issues, suggestions) { const severityCounts = { high: issues.filter(i => i.severity === 'high').length, medium: issues.filter(i => i.severity === 'medium').length, low: issues.filter(i => i.severity === 'low').length, }; return { overallHealth: calculateHealth(severityCounts), totalIssues: issues.length, criticalIssues: severityCounts.high, suggestions: suggestions.length, complexity: metrics.nestingLevel > 5 ? 'High' : metrics.nestingLevel > 3 ? 'Medium' : 'Low', }; } function calculateHealth(severityCounts) { const score = 100 - (severityCounts.high * 20 + severityCounts.medium * 10 + severityCounts.low * 5); if (score >= 80) return 'Excellent'; if (score >= 60) return 'Good'; if (score >= 40) return 'Fair'; return 'Needs Improvement'; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dvillegastech/flutter_mcp_2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server