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.addClassDeclaration((ClassDeclaration node) {
    // Check if class extends Bloc
    final ExtendsClause? extendsClause = node.extendsClause;
    if (extendsClause == null) return;

    final String superclassName = extendsClause.superclass.name.lexeme;
    if (superclassName != 'Bloc') return;

    // Find constructor
    ConstructorDeclaration? constructor;
    for (final ClassMember member in node.bodyMembers) {
      if (member is ConstructorDeclaration && member.name == null) {
        constructor = member;
        break;
      }
    }

    if (constructor == null) return;

    // Find all on<Event> calls
    final Map<String, List<MethodInvocation>> eventHandlers =
        <String, List<MethodInvocation>>{};

    final _OnCallVisitor visitor = _OnCallVisitor();
    constructor.body.accept(visitor);

    for (final MethodInvocation onCall in visitor.onCalls) {
      final TypeArgumentList? typeArgs = onCall.typeArguments;
      if (typeArgs == null || typeArgs.arguments.isEmpty) continue;

      final String eventType = typeArgs.arguments.first.toSource();
      eventHandlers
          .putIfAbsent(eventType, () => <MethodInvocation>[])
          .add(onCall);
    }

    // Report duplicates
    for (final String eventType in eventHandlers.keys) {
      final List<MethodInvocation>? handlers = eventHandlers[eventType];
      if (handlers != null && handlers.length > 1) {
        // Report all but the first one
        for (int i = 1; i < handlers.length; i++) {
          reporter.atNode(handlers[i], code);
        }
      }
    }
  });
}