visitClassDeclaration method
void
visitClassDeclaration(
- ClassDeclaration node
)
override
Implementation
@override
void visitClassDeclaration(ClassDeclaration node) {
final classBody = node.body;
if (classBody is! BlockClassBody) {
super.visitClassDeclaration(node);
return;
}
final extendsClause = node.extendsClause;
final superClassName = extendsClause?.superclass.name.lexeme;
final mixins =
node.withClause?.mixinTypes.map((t) => t.name.lexeme).toList() ?? [];
// Detect ValueNotifier subclasses
if (extendsClause != null && superClassName == 'ValueNotifier') {
final className = node.namePart.typeName.lexeme;
final typeArgs = extendsClause.superclass.typeArguments;
final valueType = (typeArgs != null && typeArgs.arguments.isNotEmpty)
? typeArgs.arguments.first.toSource()
: 'dynamic';
final methods = <MethodInfo>[];
for (final member in classBody.members) {
if (member is MethodDeclaration) {
if (member.name.lexeme == 'dispose') continue;
methods.add(buildMethodInfo(member, callsNotifyListeners: false));
}
}
nodes.add(
LogicUnitNode(
name: className,
stateFields: [FieldInfo(rawName: 'value', type: valueType)],
methods: methods,
isNotifier: true,
notifierType: NotifierType.stateNotifier,
role: 'provider',
superClassName: superClassName,
mixins: mixins,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
super.visitClassDeclaration(node);
return;
}
// Detect ChangeNotifier classes (Standard Provider pattern)
if (extendsClause != null && superClassName == 'ChangeNotifier') {
final className = node.namePart.typeName.lexeme;
final stateFields = <FieldInfo>[];
final methods = <MethodInfo>[];
bool isFamilyCandidate = false;
for (final member in classBody.members) {
if (member is FieldDeclaration) {
for (final variable in member.fields.variables) {
final typeSource = member.fields.type?.toSource() ?? 'dynamic';
final initializer = variable.initializer?.toSource();
stateFields.add(
FieldInfo(
rawName: variable.name.lexeme,
type: typeSource,
initializer: initializer,
),
);
}
} else if (member is ConstructorDeclaration) {
for (final param in member.parameters.parameters) {
final paramName = param.name?.lexeme ?? '';
if (paramName != 'key') {
isFamilyCandidate = true;
break;
}
}
} else if (member is MethodDeclaration) {
if (member.name.lexeme == 'dispose') continue;
methods.add(
buildMethodInfo(
member,
callsNotifyListeners: member.body.toSource().contains(
'notifyListeners()',
),
),
);
}
}
nodes.add(
LogicUnitNode(
name: className,
stateFields: stateFields,
methods: methods,
isNotifier: true,
notifierType: detectNotifierType(methods),
isFamilyCandidate: isFamilyCandidate,
role: _inferRole(className, superClassName),
superClassName: superClassName,
mixins: mixins,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
} else if (extendsClause != null && superClassName == 'StatelessWidget') {
final className = node.namePart.typeName.lexeme;
int buildMethodOffset = -1;
for (final member in classBody.members) {
if (member is MethodDeclaration && member.name.lexeme == 'build') {
buildMethodOffset = member.offset;
}
}
nodes.add(
WidgetNode(
widgetName: className,
widgetType: 'StatelessWidget',
buildMethodOffset: buildMethodOffset,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
} else if (extendsClause != null && superClassName == 'StatefulWidget') {
final className = node.namePart.typeName.lexeme;
int createStateOffset = -1;
for (final member in classBody.members) {
if (member is MethodDeclaration &&
member.name.lexeme == 'createState') {
createStateOffset = member.offset;
}
}
nodes.add(
WidgetNode(
widgetName: className,
widgetType: 'StatefulWidget',
buildMethodOffset: createStateOffset,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
} else if (extendsClause != null && superClassName == 'State') {
final className = node.namePart.typeName.lexeme;
final typeArguments = extendsClause.superclass.typeArguments;
if (typeArguments != null && typeArguments.arguments.isNotEmpty) {
final widgetName = typeArguments.arguments.first.toSource();
nodes.add(
StateNode(
stateClassName: className,
widgetName: widgetName,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
}
} else if (extendsClause != null && superClassName == 'HookWidget') {
final className = node.namePart.typeName.lexeme;
int buildMethodOffset = -1;
for (final member in classBody.members) {
if (member is MethodDeclaration && member.name.lexeme == 'build') {
buildMethodOffset = member.offset;
}
}
nodes.add(
HookWidgetNode(
widgetName: className,
buildMethodOffset: buildMethodOffset,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
} else if (_isLogicClass(
className: node.namePart.typeName.lexeme,
superClassName: superClassName,
)) {
// Catch-all for other logic classes (repositories, services)
final className = node.namePart.typeName.lexeme;
nodes.add(
LogicUnitNode(
name: className,
stateFields: [], // Not a framework-managed state
methods: [], // Could be expanded to scan methods
isNotifier: false,
role: _inferRole(className, superClassName),
superClassName: superClassName,
mixins: mixins,
filePath: filePath,
offset: node.offset,
length: node.length,
),
);
}
super.visitClassDeclaration(node);
}