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) {
    final String className = node.namePart.typeName.lexeme;

    // Skip private classes
    if (className.startsWith('_')) return;

    // Skip if abstract - abstract classes need different modifiers
    if (node.abstractKeyword != null) return;

    // Skip if already has a modifier
    if (node.baseKeyword != null ||
        node.finalKeyword != null ||
        node.interfaceKeyword != null ||
        node.sealedKeyword != null ||
        node.mixinKeyword != null) {
      return;
    }

    // Heuristic: Classes with only private constructors are good candidates
    bool hasPublicConstructor = false;
    bool hasAnyConstructor = false;

    for (final ClassMember member in node.body.members) {
      if (member is ConstructorDeclaration) {
        hasAnyConstructor = true;
        final Token? nameToken = member.name;
        final bool isPrivate =
            nameToken != null && nameToken.lexeme.startsWith('_');
        if (!isPrivate && member.factoryKeyword == null) {
          hasPublicConstructor = true;
        }
      }
    }

    // If no constructors defined, there's an implicit public constructor
    if (!hasAnyConstructor) {
      hasPublicConstructor = true;
    }

    // Only suggest final for classes with private-only constructors
    // or classes that look like utility/service classes
    if (!hasPublicConstructor) {
      reporter.atToken(node.namePart.typeName, code);
    }
  });
}