suggest static method

List<FixSuggestion> suggest(
  1. LayoutDecision decision
)

Generate fix suggestions for a layout decision.

Implementation

static List<FixSuggestion> suggest(LayoutDecision decision) {
  if (!decision.overflowed) return const [];

  final suggestions = <FixSuggestion>[];
  final widgetType = decision.widgetType.toLowerCase();
  final constraints = decision.constraintsReceived;
  final size = decision.sizeReported;

  final widthOverflow = size.width > constraints.maxWidth;
  final heightOverflow = size.height > constraints.maxHeight;

  // ── Row/Flex horizontal overflow ──────────────────────────────────────
  if (widthOverflow && _isHorizontalFlex(widgetType)) {
    suggestions.add(const FixSuggestion(
      description: 'Wrap overflowing children in Expanded or Flexible',
      codeHint: 'Row(children: [Expanded(child: Text(...)), ...])',
      confidence: 0.92,
      priority: 0,
      category: FixCategory.addWrapper,
    ));
    suggestions.add(const FixSuggestion(
      description: 'Make the Row horizontally scrollable',
      codeHint:
          'SingleChildScrollView(scrollDirection: Axis.horizontal, child: Row(...))',
      confidence: 0.75,
      priority: 1,
      category: FixCategory.addScrollable,
    ));
  }

  // ── Column/Flex vertical overflow ─────────────────────────────────────
  if (heightOverflow && _isVerticalFlex(widgetType)) {
    suggestions.add(const FixSuggestion(
      description: 'Wrap overflowing children in Expanded or Flexible',
      codeHint: 'Column(children: [Expanded(child: ListView(...)), ...])',
      confidence: 0.92,
      priority: 0,
      category: FixCategory.addWrapper,
    ));
    suggestions.add(const FixSuggestion(
      description: 'Wrap the Column in SingleChildScrollView',
      codeHint: 'SingleChildScrollView(child: Column(...))',
      confidence: 0.75,
      priority: 1,
      category: FixCategory.addScrollable,
    ));
  }

  // ── Text overflow ─────────────────────────────────────────────────────
  if (widthOverflow && _isTextWidget(widgetType)) {
    suggestions.add(const FixSuggestion(
      description: 'Add TextOverflow.ellipsis to truncate text',
      codeHint: "Text('...', overflow: TextOverflow.ellipsis)",
      confidence: 0.88,
      priority: 0,
      category: FixCategory.changeProperty,
    ));
    suggestions.add(const FixSuggestion(
      description: 'Wrap the Text in Flexible inside a Row or Column',
      codeHint: 'Flexible(child: Text(...))',
      confidence: 0.82,
      priority: 1,
      category: FixCategory.addWrapper,
    ));
    suggestions.add(const FixSuggestion(
      description: 'Set maxLines to limit the number of text lines',
      codeHint: "Text('...', maxLines: 2, overflow: TextOverflow.ellipsis)",
      confidence: 0.7,
      priority: 2,
      category: FixCategory.changeProperty,
    ));
  }

  // ── Image overflow ────────────────────────────────────────────────────
  if (_isImageWidget(widgetType)) {
    suggestions.add(const FixSuggestion(
      description: 'Set BoxFit.contain or BoxFit.cover on the Image',
      codeHint: 'Image.network(url, fit: BoxFit.cover)',
      confidence: 0.85,
      priority: 0,
      category: FixCategory.changeProperty,
    ));
    suggestions.add(const FixSuggestion(
      description: 'Constrain the Image with SizedBox or AspectRatio',
      codeHint: 'SizedBox(width: 200, height: 200, child: Image(...))',
      confidence: 0.8,
      priority: 1,
      category: FixCategory.constrainDimensions,
    ));
  }

  // ── ListView inside Column (unbounded height) ────────────────────────
  if (heightOverflow && _isScrollableWidget(widgetType)) {
    suggestions.add(const FixSuggestion(
      description: 'Wrap ListView in Expanded when inside a Column',
      codeHint: 'Column(children: [Expanded(child: ListView(...))])',
      confidence: 0.95,
      priority: 0,
      category: FixCategory.addWrapper,
    ));
    suggestions.add(const FixSuggestion(
      description: 'Set shrinkWrap: true on the ListView',
      codeHint:
          'ListView(shrinkWrap: true, physics: NeverScrollableScrollPhysics())',
      confidence: 0.6,
      priority: 2,
      category: FixCategory.changeProperty,
    ));
  }

  // ── Unbounded constraints ─────────────────────────────────────────────
  // When maxWidth/maxHeight is infinity, size can never exceed it numerically,
  // but an unbounded constraint IS the problem — flag it when overflowed.
  if (constraints.maxWidth == double.infinity) {
    suggestions.add(const FixSuggestion(
      description:
          'The widget received unbounded width — add a width constraint',
      codeHint:
          'ConstrainedBox(constraints: BoxConstraints(maxWidth: 400), child: ...)',
      confidence: 0.7,
      priority: 1,
      category: FixCategory.constrainDimensions,
    ));
  }
  if (constraints.maxHeight == double.infinity) {
    suggestions.add(const FixSuggestion(
      description:
          'The widget received unbounded height — add a height constraint',
      codeHint:
          'ConstrainedBox(constraints: BoxConstraints(maxHeight: 600), child: ...)',
      confidence: 0.7,
      priority: 1,
      category: FixCategory.constrainDimensions,
    ));
  }

  // ── Fallback generic suggestion ───────────────────────────────────────
  if (suggestions.isEmpty) {
    if (widthOverflow) {
      suggestions.add(const FixSuggestion(
        description:
            'Constrain the widget width with SizedBox or ConstrainedBox',
        codeHint:
            'ConstrainedBox(constraints: BoxConstraints(maxWidth: 300), child: ...)',
        confidence: 0.4,
        priority: 3,
        category: FixCategory.constrainDimensions,
      ));
    }
    if (heightOverflow) {
      suggestions.add(const FixSuggestion(
        description: 'Constrain the widget height or wrap in a scrollable',
        codeHint:
            'ConstrainedBox(constraints: BoxConstraints(maxHeight: 400), child: ...)',
        confidence: 0.4,
        priority: 3,
        category: FixCategory.constrainDimensions,
      ));
    }
  }

  // Sort by priority
  suggestions.sort((a, b) => a.priority.compareTo(b.priority));
  return suggestions;
}