From 8f64d101f68c0d91f7690121437a44435c328673 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 5 Aug 2026 17:13:21 +1000 Subject: [PATCH 1/6] [jnigen] Make Visitor constructable inline --- pkgs/jni/tool/generate_jni_bindings.dart | 49 ++++----- pkgs/jnigen/lib/src/elements/j_elements.dart | 46 ++++++++ pkgs/jnigen/test/user_visitor_test.dart | 106 ++++++++----------- 3 files changed, 111 insertions(+), 90 deletions(-) diff --git a/pkgs/jni/tool/generate_jni_bindings.dart b/pkgs/jni/tool/generate_jni_bindings.dart index 9f11c0af2f..55b3cf17c2 100644 --- a/pkgs/jni/tool/generate_jni_bindings.dart +++ b/pkgs/jni/tool/generate_jni_bindings.dart @@ -21,36 +21,25 @@ const Map _constructorAllowList = { 'Short': 's', }; -class Renamer extends Visitor { - late ClassDecl _class; - - @override - void visitClass(ClassDecl c) { - _class = c; - c.name = 'J${c.originalName}'; - } - - @override - void visitMethod(Method m) { - if (!m.isConstructor) return; - final sig = _constructorAllowList[_class.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); - } -} - Future main() async { + ClassDecl? currentClass; + final renamerVisitor = Visitor.callback( + visitClass: (c) { + currentClass = c; + c.name = 'J${c.originalName}'; + }, + visitMethod: (m) { + if (!m.isConstructor) return; + final sig = _constructorAllowList[currentClass?.originalName]; + if (sig == null) return; + final params = []; + m.accept(Visitor.callback( + visitParam: (p) => params.add(p.originalName), + )); + m.isExcluded = !(params.length == 1 && params.first == sig); + }, + ); + final classes = [ 'java.lang.Boolean', 'java.lang.Byte', @@ -95,7 +84,7 @@ Future main() async { hide: classes, preamble: preamble, generateStubs: false, - visitors: [Renamer()], + visitors: [renamerVisitor], ), ); } diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index 1dc92f61c4..b7d96d330a 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -15,6 +15,15 @@ abstract class _Element { /// Users can extend this class to create custom visitors that modify the AST /// before code generation. abstract class Visitor { + const Visitor(); + + factory Visitor.callback({ + 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,43 @@ abstract class Visitor { void visitParam(Param parameter) {} } +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; + + 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..59cddcb123 100644 --- a/pkgs/jnigen/test/user_visitor_test.dart +++ b/pkgs/jnigen/test/user_visitor_test.dart @@ -29,64 +29,6 @@ extension on Iterable { List get finalNames => map((f) => f.finalName).toList(); } -// This is customizable by the user -class UserExcluder extends Visitor { - @override - void visitClass(ClassDecl c) { - if (c.binaryName.contains('y')) { - c.isExcluded = true; - } - } - - @override - void visitMethod(Method method) { - if (method.name == 'Bar') { - method.isExcluded = true; - } - } - - @override - void visitField(Field field) { - if (field.name == 'Bar') { - field.isExcluded = true; - } - } -} - -// 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'); - } - } - - @override - void visitParam(Param parameter) { - if (parameter.originalName.contains('Foo')) { - parameter.name = parameter.originalName.replaceAll('Foo', 'Bar'); - } - } -} - Future rename(ast.Classes classes) async { final config = Config( outputConfig: OutputConfig( @@ -135,7 +77,25 @@ void main() { }); final simpleClasses = Classes(classes); - simpleClasses.accept(UserExcluder()); + simpleClasses.accept( + Visitor.callback( + 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 +141,33 @@ void main() { }); final simpleClasses = Classes(classes); - simpleClasses.accept(UserRenamer()); + simpleClasses.accept( + Visitor.callback( + 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); From 739af133008639d482b9543613230fdf598c435f Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 6 Aug 2026 09:28:36 +1000 Subject: [PATCH 2/6] super.base() --- pkgs/jni/tool/generate_jni_bindings.dart | 44 +++++++----- pkgs/jnigen/lib/src/elements/j_elements.dart | 7 +- pkgs/jnigen/test/user_visitor_test.dart | 73 +++++++++++++++++++- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/pkgs/jni/tool/generate_jni_bindings.dart b/pkgs/jni/tool/generate_jni_bindings.dart index 55b3cf17c2..f205c6f257 100644 --- a/pkgs/jni/tool/generate_jni_bindings.dart +++ b/pkgs/jni/tool/generate_jni_bindings.dart @@ -21,25 +21,31 @@ const Map _constructorAllowList = { 'Short': 's', }; -Future main() async { - ClassDecl? currentClass; - final renamerVisitor = Visitor.callback( - visitClass: (c) { - currentClass = c; - c.name = 'J${c.originalName}'; - }, - visitMethod: (m) { - if (!m.isConstructor) return; - final sig = _constructorAllowList[currentClass?.originalName]; - if (sig == null) return; - final params = []; - m.accept(Visitor.callback( - visitParam: (p) => params.add(p.originalName), - )); - m.isExcluded = !(params.length == 1 && params.first == sig); - }, - ); +class Renamer extends Visitor { + Renamer() : super.base(); + + ClassDecl? _currentClass; + + @override + void visitClass(ClassDecl c) { + _currentClass = c; + c.name = 'J${c.originalName}'; + } + @override + void visitMethod(Method m) { + if (!m.isConstructor) return; + final sig = _constructorAllowList[_currentClass?.originalName]; + if (sig == null) return; + final params = []; + m.accept(Visitor( + visitParam: (p) => params.add(p.originalName), + )); + m.isExcluded = !(params.length == 1 && params.first == sig); + } +} + +Future main() async { final classes = [ 'java.lang.Boolean', 'java.lang.Byte', @@ -84,7 +90,7 @@ Future main() async { hide: classes, preamble: preamble, generateStubs: false, - visitors: [renamerVisitor], + visitors: [Renamer()], ), ); } diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index b7d96d330a..ffac6eee08 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -15,9 +15,9 @@ abstract class _Element { /// Users can extend this class to create custom visitors that modify the AST /// before code generation. abstract class Visitor { - const Visitor(); + const Visitor.base(); - factory Visitor.callback({ + factory Visitor({ void Function(ClassDecl node)? visitClass, void Function(Method node)? visitMethod, void Function(Field node)? visitField, @@ -46,7 +46,8 @@ class _VisitorImpl extends Visitor { }) : _visitClass = visitClass, _visitMethod = visitMethod, _visitField = visitField, - _visitParam = visitParam; + _visitParam = visitParam, + super.base(); final void Function(ClassDecl node)? _visitClass; final void Function(Method node)? _visitMethod; diff --git a/pkgs/jnigen/test/user_visitor_test.dart b/pkgs/jnigen/test/user_visitor_test.dart index 59cddcb123..3a7bdfc44e 100644 --- a/pkgs/jnigen/test/user_visitor_test.dart +++ b/pkgs/jnigen/test/user_visitor_test.dart @@ -42,7 +42,76 @@ Future rename(ast.Classes classes) async { classes.accept(Renamer(config)); } +class CustomVisitor extends Visitor { + CustomVisitor() : super.base(); + + @override + void visitClass(ClassDecl c) { + if (c.binaryName.contains('y')) { + c.isExcluded = true; + } + } + + @override + void visitMethod(Method method) { + if (method.name == 'Bar') { + method.isExcluded = true; + } + } + + @override + void visitField(Field field) { + if (field.name == 'Bar') { + field.isExcluded = true; + } + } +} + 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), + ]), + }); + + final simpleClasses = Classes(classes); + simpleClasses.accept(CustomVisitor()); + + expect(classes.decls['y.Foo']?.isExcluded, true); + expect(classes.decls['Foo']?.isExcluded, false); + + 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( @@ -78,7 +147,7 @@ void main() { final simpleClasses = Classes(classes); simpleClasses.accept( - Visitor.callback( + Visitor( visitClass: (c) { if (c.binaryName.contains('y')) { c.isExcluded = true; @@ -142,7 +211,7 @@ void main() { final simpleClasses = Classes(classes); simpleClasses.accept( - Visitor.callback( + Visitor( visitClass: (c) { if (c.originalName.contains('Foo')) { c.name = c.originalName.replaceAll('Foo', 'Bar'); From 76badedfce76581afbd3dcffb2bfaa849015a0f1 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 6 Aug 2026 18:23:31 +1000 Subject: [PATCH 3/6] abstract base class --- pkgs/jnigen/lib/src/elements/j_elements.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index ffac6eee08..d738f1a5e7 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -14,7 +14,7 @@ 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({ From b3f2d7651c3c965263713cb140c06fb71fdaac06 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 6 Aug 2026 18:46:12 +1000 Subject: [PATCH 4/6] fix --- pkgs/jni/tool/generate_jni_bindings.dart | 2 +- pkgs/jnigen/example/maven_libs/example/lib/main.dart | 3 ++- pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart | 3 ++- pkgs/jnigen/lib/src/elements/j_elements.dart | 2 +- pkgs/jnigen/test/user_visitor_test.dart | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkgs/jni/tool/generate_jni_bindings.dart b/pkgs/jni/tool/generate_jni_bindings.dart index f205c6f257..269e2baa64 100644 --- a/pkgs/jni/tool/generate_jni_bindings.dart +++ b/pkgs/jni/tool/generate_jni_bindings.dart @@ -21,7 +21,7 @@ const Map _constructorAllowList = { 'Short': 's', }; -class Renamer extends Visitor { +base class Renamer extends Visitor { Renamer() : super.base(); ClassDecl? _currentClass; diff --git a/pkgs/jnigen/example/maven_libs/example/lib/main.dart b/pkgs/jnigen/example/maven_libs/example/lib/main.dart index 663a13eb51..783e48fc9c 100644 --- a/pkgs/jnigen/example/maven_libs/example/lib/main.dart +++ b/pkgs/jnigen/example/maven_libs/example/lib/main.dart @@ -39,7 +39,8 @@ class _MyAppState extends State { final json = gson.toJson(map.as(JObject.type))!.toDartString(); setState(() { - _status = ''' + _status = + ''' JSON from Gson: $json OkHttpClient: ${client.toString()}'''; }); diff --git a/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart b/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart index 0be7c65612..751c703118 100644 --- a/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart +++ b/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart @@ -39,7 +39,8 @@ class _MyAppState extends State { final json = gson.toJson(map.as(JObject.type))!.toDartString(); setState(() { - _status = ''' + _status = + ''' JSON from Gson: $json OkHttpClient: ${client.toString()}'''; }); diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index d738f1a5e7..3320501c60 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -37,7 +37,7 @@ abstract base class Visitor { void visitParam(Param parameter) {} } -class _VisitorImpl extends Visitor { +final class _VisitorImpl extends Visitor { const _VisitorImpl({ void Function(ClassDecl node)? visitClass, void Function(Method node)? visitMethod, diff --git a/pkgs/jnigen/test/user_visitor_test.dart b/pkgs/jnigen/test/user_visitor_test.dart index 3a7bdfc44e..867c27737c 100644 --- a/pkgs/jnigen/test/user_visitor_test.dart +++ b/pkgs/jnigen/test/user_visitor_test.dart @@ -42,7 +42,7 @@ Future rename(ast.Classes classes) async { classes.accept(Renamer(config)); } -class CustomVisitor extends Visitor { +base class CustomVisitor extends Visitor { CustomVisitor() : super.base(); @override From 95bec8c0b0cf270a7d8a4ba35b8a573d65802589 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 6 Aug 2026 18:57:43 +1000 Subject: [PATCH 5/6] clean up --- pkgs/jni/tool/generate_jni_bindings.dart | 2 +- pkgs/jnigen/lib/src/elements/j_elements.dart | 2 +- pkgs/jnigen/test/user_visitor_test.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/jni/tool/generate_jni_bindings.dart b/pkgs/jni/tool/generate_jni_bindings.dart index f205c6f257..269e2baa64 100644 --- a/pkgs/jni/tool/generate_jni_bindings.dart +++ b/pkgs/jni/tool/generate_jni_bindings.dart @@ -21,7 +21,7 @@ const Map _constructorAllowList = { 'Short': 's', }; -class Renamer extends Visitor { +base class Renamer extends Visitor { Renamer() : super.base(); ClassDecl? _currentClass; diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index d738f1a5e7..3320501c60 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -37,7 +37,7 @@ abstract base class Visitor { void visitParam(Param parameter) {} } -class _VisitorImpl extends Visitor { +final class _VisitorImpl extends Visitor { const _VisitorImpl({ void Function(ClassDecl node)? visitClass, void Function(Method node)? visitMethod, diff --git a/pkgs/jnigen/test/user_visitor_test.dart b/pkgs/jnigen/test/user_visitor_test.dart index 3a7bdfc44e..867c27737c 100644 --- a/pkgs/jnigen/test/user_visitor_test.dart +++ b/pkgs/jnigen/test/user_visitor_test.dart @@ -42,7 +42,7 @@ Future rename(ast.Classes classes) async { classes.accept(Renamer(config)); } -class CustomVisitor extends Visitor { +base class CustomVisitor extends Visitor { CustomVisitor() : super.base(); @override From ce206bed998fcf7c0d6cafb497cf09d1ddd6c4d4 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Thu, 6 Aug 2026 19:00:28 +1000 Subject: [PATCH 6/6] fmt --- pkgs/jnigen/example/maven_libs/example/lib/main.dart | 3 +-- pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkgs/jnigen/example/maven_libs/example/lib/main.dart b/pkgs/jnigen/example/maven_libs/example/lib/main.dart index 783e48fc9c..663a13eb51 100644 --- a/pkgs/jnigen/example/maven_libs/example/lib/main.dart +++ b/pkgs/jnigen/example/maven_libs/example/lib/main.dart @@ -39,8 +39,7 @@ class _MyAppState extends State { final json = gson.toJson(map.as(JObject.type))!.toDartString(); setState(() { - _status = - ''' + _status = ''' JSON from Gson: $json OkHttpClient: ${client.toString()}'''; }); diff --git a/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart b/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart index 751c703118..0be7c65612 100644 --- a/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart +++ b/pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart @@ -39,8 +39,7 @@ class _MyAppState extends State { final json = gson.toJson(map.as(JObject.type))!.toDartString(); setState(() { - _status = - ''' + _status = ''' JSON from Gson: $json OkHttpClient: ${client.toString()}'''; });