diff --git a/pkgs/jni/tool/generate_jni_bindings.dart b/pkgs/jni/tool/generate_jni_bindings.dart index 9f11c0af2f..269e2baa64 100644 --- a/pkgs/jni/tool/generate_jni_bindings.dart +++ b/pkgs/jni/tool/generate_jni_bindings.dart @@ -21,32 +21,27 @@ const Map _constructorAllowList = { 'Short': 's', }; -class Renamer extends Visitor { - late ClassDecl _class; +base class Renamer extends Visitor { + Renamer() : super.base(); + + ClassDecl? _currentClass; @override void visitClass(ClassDecl c) { - _class = c; + _currentClass = c; c.name = 'J${c.originalName}'; } @override void visitMethod(Method m) { if (!m.isConstructor) return; - final sig = _constructorAllowList[_class.originalName]; + final sig = _constructorAllowList[_currentClass?.originalName]; if (sig == null) return; - final lister = ListParams(); - m.accept(lister); - m.isExcluded = !(lister.params.length == 1 && lister.params.first == sig); - } -} - -class ListParams extends Visitor { - List params = []; - - @override - void visitParam(Param p) { - params.add(p.originalName); + final params = []; + m.accept(Visitor( + visitParam: (p) => params.add(p.originalName), + )); + m.isExcluded = !(params.length == 1 && params.first == sig); } } diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index 1dc92f61c4..3320501c60 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -14,7 +14,16 @@ abstract class _Element { /// /// Users can extend this class to create custom visitors that modify the AST /// before code generation. -abstract class Visitor { +abstract base class Visitor { + const Visitor.base(); + + factory Visitor({ + void Function(ClassDecl node)? visitClass, + void Function(Method node)? visitMethod, + void Function(Field node)? visitField, + void Function(Param node)? visitParam, + }) = _VisitorImpl; + /// Visits a class declaration. void visitClass(ClassDecl c) {} @@ -28,6 +37,44 @@ abstract class Visitor { void visitParam(Param parameter) {} } +final class _VisitorImpl extends Visitor { + const _VisitorImpl({ + void Function(ClassDecl node)? visitClass, + void Function(Method node)? visitMethod, + void Function(Field node)? visitField, + void Function(Param node)? visitParam, + }) : _visitClass = visitClass, + _visitMethod = visitMethod, + _visitField = visitField, + _visitParam = visitParam, + super.base(); + + final void Function(ClassDecl node)? _visitClass; + final void Function(Method node)? _visitMethod; + final void Function(Field node)? _visitField; + final void Function(Param node)? _visitParam; + + @override + void visitClass(ClassDecl c) { + _visitClass?.call(c); + } + + @override + void visitMethod(Method method) { + _visitMethod?.call(method); + } + + @override + void visitField(Field field) { + _visitField?.call(field); + } + + @override + void visitParam(Param parameter) { + _visitParam?.call(parameter); + } +} + /// A collection of class declarations. class Classes implements _Element { Classes(this._classes); diff --git a/pkgs/jnigen/test/user_visitor_test.dart b/pkgs/jnigen/test/user_visitor_test.dart index e7168b178b..867c27737c 100644 --- a/pkgs/jnigen/test/user_visitor_test.dart +++ b/pkgs/jnigen/test/user_visitor_test.dart @@ -29,8 +29,22 @@ extension on Iterable { List get finalNames => map((f) => f.finalName).toList(); } -// This is customizable by the user -class UserExcluder extends Visitor { +Future rename(ast.Classes classes) async { + final config = Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: Uri.file('test.dart'), + structure: OutputStructure.singleFile, + ), + ), + classes: []); + await classes.accept(Linker(config)); + classes.accept(Renamer(config)); +} + +base class CustomVisitor extends Visitor { + CustomVisitor() : super.base(); + @override void visitClass(ClassDecl c) { if (c.binaryName.contains('y')) { @@ -53,54 +67,51 @@ class UserExcluder extends Visitor { } } -// This is customizable by the user -class UserRenamer extends Visitor { - @override - void visitClass(ClassDecl c) { - if (c.originalName.contains('Foo')) { - c.name = c.originalName.replaceAll('Foo', 'Bar'); - } - } - - @override - void visitMethod(Method method) { - if (method.originalName.contains('Foo')) { - method.name = method.originalName.replaceAll('Foo', 'Bar'); - } - if (method.isConstructor) { - method.name = 'constructor'; - } - } - - @override - void visitField(Field field) { - if (field.originalName.contains('Foo')) { - field.name = field.originalName.replaceAll('Foo', 'Bar'); - } - } +void main() { + test('Exclude something using custom Visitor subclass', () async { + final classes = ast.Classes({ + 'Foo': ast.ClassDecl( + binaryName: 'Foo', + declKind: ast.DeclKind.classKind, + superclass: ast.DeclaredType.object, + methods: [ + ast.Method(name: 'foo', returnType: ast.DeclaredType.object), + ast.Method(name: 'Bar', returnType: ast.DeclaredType.object), + ast.Method(name: 'foo1', returnType: ast.DeclaredType.object), + ast.Method(name: 'Bar', returnType: ast.DeclaredType.object), + ], + fields: [ + ast.Field(name: 'foo', type: ast.DeclaredType.object), + ast.Field(name: 'Bar', type: ast.DeclaredType.object), + ast.Field(name: 'foo1', type: ast.DeclaredType.object), + ast.Field(name: 'Bar', type: ast.DeclaredType.object), + ], + ), + 'y.Foo': ast.ClassDecl( + binaryName: 'y.Foo', + declKind: ast.DeclKind.classKind, + superclass: ast.DeclaredType.object, + methods: [ + ast.Method(name: 'foo', returnType: ast.DeclaredType.object), + ast.Method(name: 'Bar', returnType: ast.DeclaredType.object), + ], + fields: [ + ast.Field(name: 'foo', type: ast.DeclaredType.object), + ast.Field(name: 'Bar', type: ast.DeclaredType.object), + ]), + }); - @override - void visitParam(Param parameter) { - if (parameter.originalName.contains('Foo')) { - parameter.name = parameter.originalName.replaceAll('Foo', 'Bar'); - } - } -} + final simpleClasses = Classes(classes); + simpleClasses.accept(CustomVisitor()); -Future rename(ast.Classes classes) async { - final config = Config( - outputConfig: OutputConfig( - dartConfig: DartCodeOutputConfig( - path: Uri.file('test.dart'), - structure: OutputStructure.singleFile, - ), - ), - classes: []); - await classes.accept(Linker(config)); - classes.accept(Renamer(config)); -} + expect(classes.decls['y.Foo']?.isExcluded, true); + expect(classes.decls['Foo']?.isExcluded, false); -void main() { + expect(classes.decls['Foo']?.fields.isExcludedValues, + [false, true, false, true]); + expect(classes.decls['Foo']?.methods.isExcludedValues, + [false, true, false, true]); + }); test('Exclude something using the user excluder, Simple AST', () async { final classes = ast.Classes({ 'Foo': ast.ClassDecl( @@ -135,7 +146,25 @@ void main() { }); final simpleClasses = Classes(classes); - simpleClasses.accept(UserExcluder()); + simpleClasses.accept( + Visitor( + visitClass: (c) { + if (c.binaryName.contains('y')) { + c.isExcluded = true; + } + }, + visitMethod: (method) { + if (method.name == 'Bar') { + method.isExcluded = true; + } + }, + visitField: (field) { + if (field.name == 'Bar') { + field.isExcluded = true; + } + }, + ), + ); expect(classes.decls['y.Foo']?.isExcluded, true); expect(classes.decls['Foo']?.isExcluded, false); @@ -181,7 +210,33 @@ void main() { }); final simpleClasses = Classes(classes); - simpleClasses.accept(UserRenamer()); + simpleClasses.accept( + Visitor( + visitClass: (c) { + if (c.originalName.contains('Foo')) { + c.name = c.originalName.replaceAll('Foo', 'Bar'); + } + }, + visitMethod: (method) { + if (method.originalName.contains('Foo')) { + method.name = method.originalName.replaceAll('Foo', 'Bar'); + } + if (method.isConstructor) { + method.name = 'constructor'; + } + }, + visitField: (field) { + if (field.originalName.contains('Foo')) { + field.name = field.originalName.replaceAll('Foo', 'Bar'); + } + }, + visitParam: (parameter) { + if (parameter.originalName.contains('Foo')) { + parameter.name = parameter.originalName.replaceAll('Foo', 'Bar'); + } + }, + ), + ); await rename(classes);