runWithReporter method

  1. @override
void runWithReporter(
  1. SaropaDiagnosticReporter reporter,
  2. SaropaContext context
)
override

Override this method to implement your lint rule.

Use context to register callbacks for AST node types:

context.addMethodInvocation((node) {
  if (condition) {
    reporter.atNode(node);
  }
});

Implementation

@override
void runWithReporter(
  SaropaDiagnosticReporter reporter,
  SaropaContext context,
) {
  context.addConditionalExpression((ConditionalExpression node) {
    // Check for pattern: condition ? Colors.red/error : something
    final String conditionSource = node.condition.toSource().toLowerCase();
    final String thenSource = node.thenExpression.toSource().toLowerCase();
    final String elseSource = node.elseExpression.toSource().toLowerCase();

    // cspell:ignore haserror iserror isvalid
    // Check if this is an error-related condition
    if (!conditionSource.contains('error') &&
        !conditionSource.contains('invalid') &&
        !conditionSource.contains('haserror') &&
        !conditionSource.contains('iserror') &&
        !conditionSource.contains('isvalid')) {
      return;
    }

    // Check if using error colors - use patterns to avoid false positives
    // like 'thread', 'spread', 'shredded' matching 'red'
    if (!_errorColorPattern.hasMatch(thenSource) &&
        !_errorColorPattern.hasMatch(elseSource)) {
      return;
    }

    // Require at least one branch to be Color-typed. The `\.error\b` alternative
    // in the regex matches ANY dotted identifier ending in error/Error —
    // including a log-severity enum value like `DebugLevels.Error` or a String
    // label — none of which is a UI color. A staticType==Color gate drops those
    // non-color ternaries (the accessibility concern only applies when the
    // distinction is actually carried by color).
    if (!_isColorTyped(node.thenExpression.staticType) &&
        !_isColorTyped(node.elseExpression.staticType)) {
      return;
    }

    // Check if this is in a color-only context (no icon nearby)
    // Look for Icon, errorText, or helperText in surrounding context
    AstNode? current = node.parent;
    int depth = 0;
    bool hasNonColorIndicator = false;

    while (current != null && depth < 10) {
      final String nodeSource = current.toSource();
      final bool hasIconOrText = _nonColorIndicatorPatterns
          .sublist(0, 5)
          .any((re) => re.hasMatch(nodeSource));
      final bool hasDecorationAndError =
          _nonColorIndicatorPatterns[5].hasMatch(nodeSource) &&
          _nonColorIndicatorPatterns[6].hasMatch(nodeSource);
      if (hasIconOrText || hasDecorationAndError) {
        hasNonColorIndicator = true;
        break;
      }
      current = current.parent;
      depth++;
    }

    if (!hasNonColorIndicator) {
      reporter.atNode(node);
    }
  });
}