Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 10 additions & 15 deletions pkgs/jni/tool/generate_jni_bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,26 @@ const Map<String, String> _constructorAllowList = {
};

class Renamer extends Visitor {
late ClassDecl _class;
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
47 changes: 47 additions & 0 deletions pkgs/jnigen/lib/src/elements/j_elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abstract base class

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) {}
}

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));
}

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