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 Set<String> assignedFields = <String>{};
    final Map<String, Token> nullableFields = <String, Token>{};

    // Collect nullable fields without initializers
    for (final ClassMember member in node.bodyMembers) {
      if (member is FieldDeclaration) {
        for (final VariableDeclaration variable in member.fields.variables) {
          final DartType? type = variable.declaredFragment?.element.type;
          if (type != null &&
              type.nullabilitySuffix == NullabilitySuffix.question) {
            if (variable.initializer == null) {
              nullableFields[variable.name.lexeme] = variable.name;
            }
          }
        }
      }
    }

    // Check constructors for assignments
    for (final ClassMember member in node.bodyMembers) {
      if (member is ConstructorDeclaration) {
        for (final ConstructorInitializer init in member.initializers) {
          if (init is ConstructorFieldInitializer) {
            assignedFields.add(init.fieldName.name);
          }
        }
        // A field is assigned when a constructor declares it via an
        // initializing formal (`this.x`) or a super formal (`super.x`). For
        // NAMED and OPTIONAL parameters the analyzer wraps the real parameter
        // in a DefaultFormalParameter, so unwrap before the type test —
        // otherwise a `required this.x` named parameter (the common
        // const-data-class pattern) is missed and the field is wrongly
        // reported as unassigned.
        for (final FormalParameter param in member.parameters.parameters) {
          final FormalParameter inner = param is DefaultFormalParameter
              ? param.parameter
              : param;
          if (inner is FieldFormalParameter) {
            assignedFields.add(inner.name.lexeme);
          } else if (inner is SuperFormalParameter) {
            assignedFields.add(inner.name.lexeme);
          }
        }
      }
    }

    // Check method bodies for assignments
    for (final ClassMember member in node.bodyMembers) {
      if (member is MethodDeclaration) {
        member.body.visitChildren(_FieldAssignmentVisitor(assignedFields));
      }
      if (member is ConstructorDeclaration &&
          member.body is BlockFunctionBody) {
        member.body.visitChildren(_FieldAssignmentVisitor(assignedFields));
      }
    }

    // Report unassigned fields
    for (final MapEntry<String, Token> entry in nullableFields.entries) {
      if (!assignedFields.contains(entry.key)) {
        reporter.atToken(entry.value, code);
      }
    }
  });
}