Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 11 additions & 16 deletions pkgs/jni/tool/generate_jni_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,27 @@ const Map<String, String> _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<String> params = [];

@override
void visitParam(Param p) {
params.add(p.originalName);
final params = <String>[];
m.accept(Visitor(
visitParam: (p) => params.add(p.originalName),
));
m.isExcluded = !(params.length == 1 && params.first == sig);
}
}

Expand Down
3 changes: 2 additions & 1 deletion pkgs/jnigen/example/maven_libs/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class _MyAppState extends State<MyApp> {
final json = gson.toJson(map.as(JObject.type))!.toDartString();

setState(() {
_status = '''
_status =
'''
JSON from Gson: $json
OkHttpClient: ${client.toString()}''';
});
Expand Down
3 changes: 2 additions & 1 deletion pkgs/jnigen/example/maven_libs_groovy/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class _MyAppState extends State<MyApp> {
final json = gson.toJson(map.as(JObject.type))!.toDartString();

setState(() {
_status = '''
_status =
'''
JSON from Gson: $json
OkHttpClient: ${client.toString()}''';
});
Expand Down
49 changes: 48 additions & 1 deletion pkgs/jnigen/lib/src/elements/j_elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}

Expand All @@ -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);
Expand Down
153 changes: 104 additions & 49 deletions pkgs/jnigen/test/user_visitor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,22 @@ extension on Iterable<ast.Field> {
List<String> get finalNames => map((f) => f.finalName).toList();
}

// This is customizable by the user
class UserExcluder extends Visitor {
Future<void> 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')) {
Expand All @@ -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<void> 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(
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
Loading