runWithReporter method
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);
}
}
});
}