Reputation: 1045
I am trying to make a source generator that would mimic C# anonymous objects, because they are great for when you are manipulating with collections (Select
, GroupBy
, etc.).
Imagine this code:
class Person {
final String firstName;
final String lastName;
final int age;
Person(this.firstName, this.age, this.lastName);
}
class TestClass {
final _data = [
Person('John', 'Doe', 51),
Person('Jane', 'Doe', 50),
Person('John', 'Smith', 40),
];
void testMethod() {
final map1 = _data.map((p) => _$$1(name: p.firstName, age: p.age));
final map2 = _data.map((p) => _$$2(fullName: '${p.firstName} ${p.lastName}', age: p.age));
}
}
Those _$$x
objects are what I want to generate now. I need to somehow find them and find what is being passed into them, so my code generator would generate this:
class _$$1 {
final String name;
final int age;
const _$$1({required this.name, required this.age});
}
class _$$2 {
final String fullName;
final int age;
const _$$1({required this.fullName, required this.age});
}
but I cannot seem to even find method content:
FutureOr<String?> generate(LibraryReader library, BuildStep buildStep) {
for (final clazz in library.classes) {
final method = clazz.methods.first;
method.visitChildren(RecursiveElementVisitor<dynamic>());
}
}
it looks like the MethodElement
doesn't have any children? so this doesn't look like the right way.
Is there any other way to find what I need?
Upvotes: 1
Views: 136
Reputation: 3678
A visitor can be used at the lower-level Abstract Syntax Tree to find the _$$x
constructor invocations.
The visitor should also visit the whole library rather than just classes as is done in your example, in order to locate top-level usages as well.
The AST does not distinguish between constructor and method invocations, but we can use a series of checks to make sure that the invocation in question is an appropriate target for code generation nonetheless. In a similar fashion, checks can also be put in place to ensure that the invocation is done in a closure.
The following example implements this approach, and leaves you with a map of '_$$x'
to MethodInvocation
s to work with:
FutureOr<String?> generate(LibraryReader library, BuildStep buildStep) {
final libraryElement = libraryReader.element;
final parsedLibraryResult = libraryElement.session
.getParsedLibraryByElement(libraryElement) as ParsedLibraryResult;
final libraryCompilationUnit = parsedLibraryResult.units[0].unit;
final selectorInstantiationLocator = SelectorInstantiationLocator();
libraryCompilationUnit.visitChildren(selectorInstantiationLocator);
final selectorInstantiations =
selectorInstantiationLocator.selectorInstantiations;
// ...
}
class SelectorInstantiationLocator extends RecursiveAstVisitor<void> {
final selectorInstantiations = <String, MethodInvocation>{};
@override
void visitMethodInvocation(MethodInvocation node) {
// Ensure that the invocation is an appropriate target for code generation.
// &= is not used in favour of the short-circuit && operator (https://github.com/dart-lang/language/issues/23).
// Stop if the invocation doesn't match the required prefix.
final className = node.methodName.name;
var isSelectorInstantiation = className.startsWith(r'_$$');
final classIndex = int.tryParse(className.substring(3));
isSelectorInstantiation =
isSelectorInstantiation && (classIndex != null && classIndex >= 0);
// No target will exist for a constructor invocation.
isSelectorInstantiation =
isSelectorInstantiation && node.realTarget == null;
// The selector instantiation should be done in an expression function body (=>).
isSelectorInstantiation =
isSelectorInstantiation && node.parent is ExpressionFunctionBody;
// The function body should be part of a function expression (rather than a method declaration)
isSelectorInstantiation =
isSelectorInstantiation && node.parent!.parent is FunctionExpression;
// The function expression should be inside an argument list.
isSelectorInstantiation =
isSelectorInstantiation && node.parent!.parent!.parent is ArgumentList;
if (isSelectorInstantiation) selectorInstantiations[className] = node;
return super.visitMethodInvocation(node);
}
}
Upvotes: 1