Skip to main content
Glama
renderDiagnostics.js15.1 kB
import { parseWidgetTree } from '../utils/treeParser.js'; export async function diagnoseRenderIssues(args) { const { widgetCode, includeVisualizations = true, checkConstraints = true } = args; try { const issues = { overflowErrors: [], constraintViolations: [], infiniteDimensions: [], layoutIssues: [], solutions: [], }; // Detect overflow issues issues.overflowErrors = detectOverflowIssues(widgetCode); // Check constraint violations if (checkConstraints) { issues.constraintViolations = detectConstraintViolations(widgetCode); } // Find infinite dimension problems issues.infiniteDimensions = detectInfiniteDimensions(widgetCode); // General layout issues issues.layoutIssues = detectLayoutIssues(widgetCode); // Generate solutions issues.solutions = generateSolutions(issues); // Create debug overlays if requested const debugCode = includeVisualizations ? generateDebugOverlays(widgetCode, issues) : null; const severity = calculateSeverity(issues); const fixPriority = prioritizeFixes(issues); return { content: [ { type: 'text', text: JSON.stringify({ issues, severity, fixPriority, debugCode, summary: generateDiagnosticSummary(issues), preventionTips: generatePreventionTips(issues), }, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error diagnosing render issues: ${error.message}`, }, ], }; } } function detectOverflowIssues(code) { const overflowIssues = []; // Check for Row overflow if (code.includes('Row') && !code.includes('Flexible') && !code.includes('Expanded')) { const rowMatches = code.matchAll(/Row\s*\([^)]*children:\s*\[[^\]]*\]/gs); for (const match of rowMatches) { const rowContent = match[0]; const childCount = (rowContent.match(/,/g) || []).length + 1; if (childCount > 3 && !rowContent.includes('MainAxisSize.min')) { overflowIssues.push({ type: 'horizontal_overflow', widget: 'Row', location: findLineNumber(code, match.index), severity: 'high', message: 'Row with multiple children may overflow horizontally', code: rowContent.substring(0, 100) + '...', }); } } } // Check for Column in Column without proper constraints if (code.includes('Column')) { const nestedColumns = /Column[^{]*{[^}]*Column/gs.test(code); if (nestedColumns && !code.includes('Expanded') && !code.includes('Flexible')) { overflowIssues.push({ type: 'vertical_overflow', widget: 'Column', severity: 'high', message: 'Nested Columns without Expanded/Flexible may cause overflow', }); } } // Check for Text overflow const textOverflowPattern = /Text\s*\([^)]+\)/g; const textMatches = code.matchAll(textOverflowPattern); for (const match of textMatches) { if (!match[0].includes('overflow:') && !match[0].includes('maxLines:')) { overflowIssues.push({ type: 'text_overflow', widget: 'Text', location: findLineNumber(code, match.index), severity: 'medium', message: 'Text widget without overflow handling', suggestion: 'Add overflow: TextOverflow.ellipsis', }); } } return overflowIssues; } function detectConstraintViolations(code) { const violations = []; // Container with both color and decoration const containerPattern = /Container\s*\([^)]*\)/gs; const containers = code.matchAll(containerPattern); for (const match of containers) { const containerCode = match[0]; if (containerCode.includes('color:') && containerCode.includes('decoration:')) { violations.push({ type: 'container_color_decoration', widget: 'Container', location: findLineNumber(code, match.index), severity: 'error', message: 'Container cannot have both color and decoration', fix: 'Move color inside BoxDecoration', }); } } // Unbounded height/width if (code.includes('height: double.infinity') || code.includes('width: double.infinity')) { const parent = findParentWidget(code, code.indexOf('double.infinity')); violations.push({ type: 'unbounded_dimensions', severity: 'high', message: 'Widget with infinite dimensions needs bounded parent', parent, fix: 'Wrap with Container with fixed dimensions or use Expanded', }); } // ListView inside Column without proper constraints if (code.includes('ListView') && code.includes('Column')) { const listViewInColumn = /Column[^}]*ListView(?!\.builder)/; if (listViewInColumn.test(code) && !code.includes('Expanded') && !code.includes('shrinkWrap: true')) { violations.push({ type: 'listview_unbounded_height', severity: 'error', message: 'ListView inside Column needs bounded height', fix: 'Either wrap ListView with Expanded or set shrinkWrap: true', }); } } return violations; } function detectInfiniteDimensions(code) { const infiniteIssues = []; // Stack without positioned children if (code.includes('Stack')) { const stackPattern = /Stack\s*\([^)]*children:\s*\[[^\]]*\]/gs; const stacks = code.matchAll(stackPattern); for (const match of stacks) { if (!match[0].includes('Positioned')) { infiniteIssues.push({ type: 'stack_infinite_size', widget: 'Stack', severity: 'warning', message: 'Stack without Positioned children takes infinite size', suggestion: 'Use Positioned or give Stack explicit dimensions', }); } } } // IntrinsicHeight/Width performance warning if (code.includes('IntrinsicHeight') || code.includes('IntrinsicWidth')) { infiniteIssues.push({ type: 'intrinsic_performance', severity: 'warning', message: 'IntrinsicHeight/Width can be expensive', suggestion: 'Consider alternatives if used in scrollable lists', }); } // CustomScrollView without slivers if (code.includes('CustomScrollView') && !code.includes('SliverList')) { infiniteIssues.push({ type: 'custom_scroll_view_empty', severity: 'error', message: 'CustomScrollView needs sliver children', }); } return infiniteIssues; } function detectLayoutIssues(code) { const layoutIssues = []; // Padding inside Padding const doublePadding = /Padding[^}]*Padding/; if (doublePadding.test(code)) { layoutIssues.push({ type: 'redundant_padding', severity: 'low', message: 'Nested Padding widgets can be combined', optimization: 'Combine padding values into single Padding widget', }); } // Multiple Containers const multipleContainers = /Container[^}]*Container/; if (multipleContainers.test(code)) { layoutIssues.push({ type: 'redundant_containers', severity: 'low', message: 'Nested Containers can often be combined', optimization: 'Merge Container properties', }); } // Incorrect Flex usage if (code.includes('Flex') && !code.includes('direction:')) { layoutIssues.push({ type: 'flex_missing_direction', severity: 'error', message: 'Flex widget requires direction parameter', }); } // Center inside Center if (/Center[^}]*Center/.test(code)) { layoutIssues.push({ type: 'redundant_center', severity: 'low', message: 'Nested Center widgets are redundant', }); } return layoutIssues; } function generateSolutions(issues) { const solutions = []; // Solutions for overflow errors issues.overflowErrors.forEach(error => { if (error.type === 'horizontal_overflow') { solutions.push({ issue: error.type, solution: 'Wrap children with Flexible or Expanded', code: `Row( children: [ Expanded( child: YourWidget(), ), Flexible( child: AnotherWidget(), ), ], )`, }); } else if (error.type === 'text_overflow') { solutions.push({ issue: error.type, solution: 'Add overflow handling to Text', code: `Text( 'Your long text here', overflow: TextOverflow.ellipsis, maxLines: 2, )`, }); } }); // Solutions for constraint violations issues.constraintViolations.forEach(violation => { if (violation.type === 'container_color_decoration') { solutions.push({ issue: violation.type, solution: 'Move color inside BoxDecoration', code: `Container( decoration: BoxDecoration( color: Colors.blue, // other decoration properties ), child: YourWidget(), )`, }); } else if (violation.type === 'listview_unbounded_height') { solutions.push({ issue: violation.type, solution: 'Two ways to fix ListView in Column', code: `// Option 1: Wrap with Expanded Column( children: [ Expanded( child: ListView(...), ), ], ) // Option 2: Use shrinkWrap Column( children: [ ListView( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), ... ), ], )`, }); } }); return solutions; } function generateDebugOverlays(code, issues) { let debugCode = code; // Add debug paint properties const debugOverlay = ` // Debug overlay to visualize layout issues class DebugOverlay extends StatelessWidget { final Widget child; const DebugOverlay({required this.child}); @override Widget build(BuildContext context) { return Stack( children: [ child, if (kDebugMode) ...[ // Render overflow indicator Positioned( top: 0, right: 0, child: Container( padding: EdgeInsets.all(4), color: Colors.red, child: Text( 'OVERFLOW', style: TextStyle(color: Colors.white, fontSize: 10), ), ), ), ], ], ); } } // Enable visual debugging void enableLayoutDebugging() { debugPaintSizeEnabled = true; debugPaintBaselinesEnabled = true; debugPaintLayerBordersEnabled = true; debugPaintPointersEnabled = true; debugRepaintRainbowEnabled = true; }`; // Add constraint debugger const constraintDebugger = ` // Widget to debug constraints class ConstraintDebugger extends StatelessWidget { final Widget child; final String label; const ConstraintDebugger({ required this.child, required this.label, }); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { return Stack( children: [ child, if (kDebugMode) Positioned( top: 0, left: 0, child: Container( padding: EdgeInsets.all(2), color: Colors.black87, child: Text( '$label\\nW: ${constraints.maxWidth.toStringAsFixed(1)}\\nH: ${constraints.maxHeight.toStringAsFixed(1)}', style: TextStyle(color: Colors.white, fontSize: 8), ), ), ), ], ); }, ); } }`; return debugOverlay + '\n\n' + constraintDebugger; } function findLineNumber(code, index) { const lines = code.substring(0, index).split('\n'); return lines.length; } function findParentWidget(code, childIndex) { const beforeChild = code.substring(0, childIndex); const widgets = ['Container', 'Row', 'Column', 'Stack', 'Scaffold']; let lastWidget = 'Unknown'; let lastIndex = -1; widgets.forEach(widget => { const index = beforeChild.lastIndexOf(widget); if (index > lastIndex) { lastIndex = index; lastWidget = widget; } }); return lastWidget; } function calculateSeverity(issues) { let score = 0; const allIssues = [ ...issues.overflowErrors, ...issues.constraintViolations, ...issues.infiniteDimensions, ...issues.layoutIssues, ]; allIssues.forEach(issue => { if (issue.severity === 'error') score += 10; else if (issue.severity === 'high') score += 7; else if (issue.severity === 'medium') score += 4; else if (issue.severity === 'warning') score += 2; else score += 1; }); if (score === 0) return 'none'; if (score < 10) return 'low'; if (score < 25) return 'medium'; if (score < 50) return 'high'; return 'critical'; } function prioritizeFixes(issues) { const priorities = []; // First priority: Errors that prevent rendering issues.constraintViolations .filter(v => v.severity === 'error') .forEach(violation => { priorities.push({ priority: 1, issue: violation.type, action: violation.fix, impact: 'Prevents widget from rendering', }); }); // Second priority: Overflow issues issues.overflowErrors.forEach(error => { priorities.push({ priority: 2, issue: error.type, action: error.suggestion || 'Fix overflow', impact: 'Visible rendering issues', }); }); // Third priority: Performance issues issues.infiniteDimensions .filter(i => i.type === 'intrinsic_performance') .forEach(issue => { priorities.push({ priority: 3, issue: issue.type, action: issue.suggestion, impact: 'Performance degradation', }); }); return priorities.sort((a, b) => a.priority - b.priority); } function generateDiagnosticSummary(issues) { const totalIssues = issues.overflowErrors.length + issues.constraintViolations.length + issues.infiniteDimensions.length + issues.layoutIssues.length; return { totalIssues, criticalIssues: issues.constraintViolations.filter(v => v.severity === 'error').length, overflowRisk: issues.overflowErrors.length > 0, performanceIssues: issues.infiniteDimensions.some(i => i.type === 'intrinsic_performance'), recommendation: totalIssues > 5 ? 'Major refactoring recommended' : totalIssues > 0 ? 'Minor fixes needed' : 'No render issues detected', }; } function generatePreventionTips(issues) { const tips = [ 'Always test layouts on different screen sizes', 'Use Flutter Inspector to visualize constraints', 'Enable debug painting during development', ]; if (issues.overflowErrors.length > 0) { tips.push('Use Flexible/Expanded for dynamic content in Row/Column'); } if (issues.constraintViolations.length > 0) { tips.push('Understand Flutter\'s constraint system: "Constraints go down, Sizes go up"'); } if (issues.layoutIssues.length > 0) { tips.push('Avoid unnecessary nesting of layout widgets'); } return tips; }

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