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,
) {
  final realtimeChannelRegex = RegExp(r'\bRealtimeChannel\b');
  final removeChannelRegex = RegExp(r'\bremoveChannel\b');
  RegExp unsubscribeRegex(String name) =>
      RegExp(RegExp.escape(name) + r'\s*[?.]?\s*\.\s*unsubscribe\s*\(');
  context.addClassDeclaration((ClassDeclaration node) {
    final ExtendsClause? extendsClause = node.extendsClause;
    if (extendsClause == null) return;

    final String superName = extendsClause.superclass.name.lexeme;
    if (superName != 'State') return;
    // Find RealtimeChannel fields
    final List<String> channelNames = <String>[];
    for (final ClassMember member in node.bodyMembers) {
      if (member is FieldDeclaration) {
        final String? typeName = member.fields.type?.toSource();
        if (typeName != null && realtimeChannelRegex.hasMatch(typeName)) {
          for (final VariableDeclaration variable
              in member.fields.variables) {
            channelNames.add(variable.name.lexeme);
          }
        }
      }
    }

    if (channelNames.isEmpty) return;

    // Find dispose method
    String? disposeBody;
    for (final ClassMember member in node.bodyMembers) {
      if (member is MethodDeclaration && member.name.lexeme == 'dispose') {
        disposeBody = member.body.toSource();
        break;
      }
    }

    // Check if channels are unsubscribed
    for (final String name in channelNames) {
      final bool isUnsubscribed =
          disposeBody != null &&
          (unsubscribeRegex(name).hasMatch(disposeBody) ||
              removeChannelRegex.hasMatch(disposeBody));

      if (!isUnsubscribed) {
        for (final ClassMember member in node.bodyMembers) {
          if (member is FieldDeclaration) {
            for (final VariableDeclaration variable
                in member.fields.variables) {
              if (variable.name.lexeme == name) {
                reporter.atNode(variable);
              }
            }
          }
        }
      }
    }
  });
}