diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index ac7625e21f..b365f8b0b4 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -2,6 +2,7 @@ * [front-end] Added a more explicit error if generic fields are used. * [front-end] Added a more explicit error for static fields. +* [front-end] Added more errors for incorrect usage of Pigeon (previously they were just ignored). ## 0.3.0 diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 4d78eca8bf..bfc438fe8d 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -21,7 +21,10 @@ class Method extends Node { required this.name, required this.returnType, required this.argType, + this.isArgNullable = false, this.isAsynchronous = false, + this.isReturnNullable = false, + this.offset, }); /// The name of the method. @@ -30,12 +33,21 @@ class Method extends Node { /// The data-type of the return value. String returnType; + /// True if the method can return a null value. + bool isReturnNullable; + /// The data-type of the argument. String argType; + /// True if the argument has a null tag `?`. + bool isArgNullable; + /// Whether the receiver of this method is expected to return synchronously or not. bool isAsynchronous; + /// The offset in the source file where the field appears. + int? offset; + @override String toString() { return '(Api name:$name returnType:$returnType argType:$argType isAsynchronous:$isAsynchronous)'; @@ -76,6 +88,8 @@ class Field extends Node { Field({ required this.name, required this.dataType, + required this.isNullable, + this.typeArguments, this.offset, }); @@ -88,6 +102,12 @@ class Field extends Node { /// The offset in the source file where the field appears. int? offset; + /// True if the datatype is nullable (ex `int?`). + bool isNullable; + + /// Type parameters used for generics. + List? typeArguments; + @override String toString() { return '(Field name:$name dataType:$dataType)'; diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index b4dc9a18f3..af647990fb 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -391,7 +391,7 @@ List _validateAst(Root root, String source) { final List customEnums = root.enums.map((Enum x) => x.name).toList(); for (final Class klass in root.classes) { for (final Field field in klass.fields) { - if (field.dataType.contains('<')) { + if (field.typeArguments != null) { result.add(Error( message: 'Unsupported datatype:"${field.dataType}" in class "${klass.name}". Generic fields aren\'t yet supported (https://github.com/flutter/flutter/issues/63468).', @@ -410,15 +410,33 @@ List _validateAst(Root root, String source) { } for (final Api api in root.apis) { for (final Method method in api.methods) { + if (method.isReturnNullable) { + result.add(Error( + message: + 'Nullable return types types aren\'t supported for Pigeon methods: "${method.argType}" in API: "${api.name}" method: "${method.name}', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); + } + if (method.isArgNullable) { + result.add(Error( + message: + 'Nullable argument types aren\'t supported for Pigeon methods: "${method.argType}" in API: "${api.name}" method: "${method.name}', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); + } if (_validTypes.contains(method.argType)) { result.add(Error( - message: - 'Unsupported argument type: "${method.argType}" in API: "${api.name}" method: "${method.name}')); + message: + 'Primitive argument types aren\'t yet supported (https://github.com/flutter/flutter/issues/66467): "${method.argType}" in API: "${api.name}" method: "${method.name}', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); } if (_validTypes.contains(method.returnType)) { result.add(Error( - message: - 'Unsupported return type: "${method.returnType}" in API: "${api.name}" method: "${method.name}')); + message: + 'Primitive return types aren\'t yet supported (https://github.com/flutter/flutter/issues/66467): "${method.returnType}" in API: "${api.name}" method: "${method.name}', + lineNumber: _calculateLineNumberNullable(source, method.offset), + )); } } } @@ -426,6 +444,17 @@ List _validateAst(Root root, String source) { return result; } +class _FindInitializer extends dart_ast_visitor.RecursiveAstVisitor { + dart_ast.Expression? initializer; + @override + Object? visitVariableDeclaration(dart_ast.VariableDeclaration node) { + if (node.initializer != null) { + initializer = node.initializer; + } + return null; + } +} + class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { _RootBuilder(this.source, this.ignoresInvalidImports); @@ -472,30 +501,11 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { } } - final List classesWithNullTagStripped = _classes.map((Class aClass) { - return Class( - name: aClass.name, - fields: aClass.fields.map((Field field) { - String datatype = field.dataType; - if (datatype.endsWith('?')) { - datatype = datatype.substring(0, datatype.length - 1); - } else { - // TODO(aaclarke): Provide an error when not using a nullable type. - // _errors.add(Error( - // message: - // 'Field ${aClass.name}.${field.name} must be nullable.')); - } - return Field( - name: field.name, dataType: datatype, offset: field.offset); - }).toList()); - }).toList(); - final List classesToCheck = List.from(referencedTypes); while (classesToCheck.isNotEmpty) { final String next = classesToCheck.last; classesToCheck.removeLast(); - final Class aClass = classesWithNullTagStripped.firstWhere( - (Class x) => x.name == next, + final Class aClass = _classes.firstWhere((Class x) => x.name == next, orElse: () => Class(name: '', fields: [])); for (final Field field in aClass.fields) { if (!referencedTypes.contains(field.dataType) && @@ -510,8 +520,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { ? (Class x) => !referencedTypes.contains(x.name) : (Class x) => !referencedTypes.contains(x.name) && !typeFilter.contains(x.name); - final List referencedClasses = - List.from(classesWithNullTagStripped); + final List referencedClasses = List.from(_classes); referencedClasses.removeWhere(classRemover); final List referencedEnums = List.from(_enums); @@ -646,6 +655,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { Object? visitMethodDeclaration(dart_ast.MethodDeclaration node) { final dart_ast.FormalParameterList parameters = node.parameters!; late String argType; + bool isNullable = false; if (parameters.parameters.isEmpty) { argType = 'void'; } else { @@ -655,6 +665,7 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { // ignore: always_specify_types .firstWhere((e) => e is dart_ast.TypeName) as dart_ast.TypeName; argType = typeName.name.name; + isNullable = typeName.question != null; } final bool isAsynchronous = _hasMetadata(node.metadata, 'async'); if (_currentApi != null) { @@ -662,7 +673,15 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { name: node.name.name, returnType: node.returnType.toString(), argType: argType, - isAsynchronous: isAsynchronous)); + isReturnNullable: node.returnType!.question != null, + isArgNullable: isNullable, + isAsynchronous: isAsynchronous, + offset: node.offset)); + } else if (_currentClass != null) { + _errors.add(Error( + message: + 'Methods aren\'t supported in Pigeon data classes ("${node.name.name}").', + lineNumber: _calculateLineNumber(source, node.offset))); } node.visitChildren(this); return null; @@ -689,20 +708,59 @@ class _RootBuilder extends dart_ast_visitor.RecursiveAstVisitor { 'Pigeon doesn\'t support static fields ("${node.toString()}"), consider using enums.', lineNumber: _calculateLineNumber(source, node.offset))); } else if (type is dart_ast.NamedType) { - _currentClass!.fields.add(Field( - name: node.fields.variables[0].name.name, - dataType: type.toString(), - offset: node.offset, - )); + final _FindInitializer findInitializerVisitor = _FindInitializer(); + node.visitChildren(findInitializerVisitor); + if (findInitializerVisitor.initializer != null) { + _errors.add(Error( + message: + 'Initialization isn\'t supported for fields in Pigeon data classes ("$node"), just use nullable types with no initializer (example "int? x;").', + lineNumber: _calculateLineNumber(source, node.offset))); + } else { + final dart_ast.TypeArgumentList? typeArguments = type.typeArguments; + _currentClass!.fields.add(Field( + name: node.fields.variables[0].name.name, + dataType: type.name.name, + isNullable: type.question != null, + // TODO(aaclarke): This probably has to be recursive at some point. + // ignore: prefer_null_aware_operators + typeArguments: typeArguments == null + ? null + : typeArguments.arguments + .map((dart_ast.TypeAnnotation e) => Field( + name: '', + dataType: (e.childEntities.first + as dart_ast.SimpleIdentifier) + .name, + isNullable: e.question != null, + offset: e.offset, + )) + .toList(), + offset: node.offset, + )); + } } else { _errors.add(Error( message: 'Expected a named type but found "${node.toString()}".', lineNumber: _calculateLineNumber(source, node.offset))); } + } else if (_currentApi != null) { + _errors.add(Error( + message: 'Fields aren\'t supported in Pigeon API classes ("$node").', + lineNumber: _calculateLineNumber(source, node.offset))); } node.visitChildren(this); return null; } + + @override + Object? visitConstructorDeclaration(dart_ast.ConstructorDeclaration node) { + final String type = _currentApi != null ? 'API classes' : 'data classes'; + _errors.add(Error( + message: 'Constructors aren\'t supported in $type ("$node").', + lineNumber: _calculateLineNumber(source, node.offset))); + node.visitChildren(this); + return null; + } } int? _calculateLineNumberNullable(String contents, int? offset) { diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 5c52a3e91b..156603c4ae 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -15,6 +15,7 @@ void main() { Field( name: 'field1', dataType: 'dataType1', + isNullable: true, ), ], ); @@ -57,17 +58,26 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -80,11 +90,23 @@ void main() { final Root root = Root(apis: [], classes: [ Class( name: 'Input', - fields: [Field(name: 'input', dataType: 'String')], + fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ], ), Class( name: 'Nested', - fields: [Field(name: 'nested', dataType: 'Input')], + fields: [ + Field( + name: 'nested', + dataType: 'Input', + isNullable: true, + ) + ], ) ], enums: []); final StringBuffer sink = StringBuffer(); @@ -110,17 +132,26 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -135,14 +166,19 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -157,14 +193,19 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -183,14 +224,19 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -205,14 +251,19 @@ void main() { Method( name: 'doSomething', argType: 'EnumClass', + isArgNullable: false, returnType: 'EnumClass', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'EnumClass', - fields: [Field(name: 'enum1', dataType: 'Enum')]), + Class(name: 'EnumClass', fields: [ + Field( + name: 'enum1', + dataType: 'Enum', + isNullable: true, + ) + ]), ], enums: [ Enum( name: 'Enum', @@ -237,14 +288,19 @@ void main() { Method( name: 'doSomething', argType: 'EnumClass', + isArgNullable: false, returnType: 'EnumClass', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'EnumClass', - fields: [Field(name: 'enum1', dataType: 'Enum')]), + Class(name: 'EnumClass', fields: [ + Field( + name: 'enum1', + dataType: 'Enum', + isNullable: true, + ) + ]), ], enums: [ Enum( name: 'Enum', @@ -271,14 +327,19 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -296,23 +357,33 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ), Method( name: 'voidReturner', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer mainCodeSink = StringBuffer(); final StringBuffer testCodeSink = StringBuffer(); @@ -342,6 +413,7 @@ void main() { Field( name: 'field1', dataType: 'dataType1', + isNullable: true, ), ], ); @@ -362,17 +434,26 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: true, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -389,17 +470,26 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: true, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -415,17 +505,26 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: true, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); @@ -440,14 +539,19 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: true, ) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateDart(const DartOptions(isNullSafe: false), root, sink); diff --git a/packages/pigeon/test/java_generator_test.dart b/packages/pigeon/test/java_generator_test.dart index 94feb1f091..9167aa920c 100644 --- a/packages/pigeon/test/java_generator_test.dart +++ b/packages/pigeon/test/java_generator_test.dart @@ -14,6 +14,7 @@ void main() { Field( name: 'field1', dataType: 'int', + isNullable: true, ), ], ); @@ -63,6 +64,7 @@ void main() { Field( name: 'field1', dataType: 'int', + isNullable: true, ) ], ); @@ -86,17 +88,18 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: true, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field(name: 'input', dataType: 'String', isNullable: true) + ]), + Class(name: 'Output', fields: [ + Field(name: 'output', dataType: 'String', isNullable: true) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -110,14 +113,14 @@ void main() { test('all the simple datatypes header', () { final Root root = Root(apis: [], classes: [ Class(name: 'Foobar', fields: [ - Field(name: 'aBool', dataType: 'bool'), - Field(name: 'aInt', dataType: 'int'), - Field(name: 'aDouble', dataType: 'double'), - Field(name: 'aString', dataType: 'String'), - Field(name: 'aUint8List', dataType: 'Uint8List'), - Field(name: 'aInt32List', dataType: 'Int32List'), - Field(name: 'aInt64List', dataType: 'Int64List'), - Field(name: 'aFloat64List', dataType: 'Float64List'), + Field(name: 'aBool', dataType: 'bool', isNullable: true), + Field(name: 'aInt', dataType: 'int', isNullable: true), + Field(name: 'aDouble', dataType: 'double', isNullable: true), + Field(name: 'aString', dataType: 'String', isNullable: true), + Field(name: 'aUint8List', dataType: 'Uint8List', isNullable: true), + Field(name: 'aInt32List', dataType: 'Int32List', isNullable: true), + Field(name: 'aInt64List', dataType: 'Int64List', isNullable: true), + Field(name: 'aFloat64List', dataType: 'Float64List', isNullable: true), ]), ], enums: []); @@ -141,17 +144,18 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field(name: 'input', dataType: 'String', isNullable: true) + ]), + Class(name: 'Output', fields: [ + Field(name: 'output', dataType: 'String', isNullable: true) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -167,14 +171,15 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field(name: 'input', dataType: 'String', isNullable: true) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -190,14 +195,15 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field(name: 'input', dataType: 'String', isNullable: true) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -214,14 +220,15 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field(name: 'output', dataType: 'String', isNullable: true) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -237,14 +244,15 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: false, ) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field(name: 'output', dataType: 'String', isNullable: true) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -256,9 +264,9 @@ void main() { test('gen list', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'List')]), + Class(name: 'Foobar', fields: [ + Field(name: 'field1', dataType: 'List', isNullable: true) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -270,9 +278,9 @@ void main() { test('gen map', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'Map')]), + Class(name: 'Foobar', fields: [ + Field(name: 'field1', dataType: 'Map', isNullable: true) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -289,6 +297,7 @@ void main() { Field( name: 'nested', dataType: 'Nested', + isNullable: true, ) ], ); @@ -298,6 +307,7 @@ void main() { Field( name: 'data', dataType: 'int', + isNullable: true, ) ], ); @@ -324,17 +334,18 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: true, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field(name: 'input', dataType: 'String', isNullable: true) + ]), + Class(name: 'Output', fields: [ + Field(name: 'output', dataType: 'String', isNullable: true) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -357,17 +368,18 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: true, ) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field(name: 'input', dataType: 'String', isNullable: true) + ]), + Class(name: 'Output', fields: [ + Field(name: 'output', dataType: 'String', isNullable: true) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); const JavaOptions javaOptions = JavaOptions(className: 'Messages'); @@ -391,6 +403,7 @@ void main() { Field( name: 'enum1', dataType: 'Enum1', + isNullable: true, ), ], ); diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index f38a788df4..383e84ebd6 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -9,9 +9,13 @@ import 'package:test/test.dart'; void main() { test('gen one class header', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'String')]), + Class(name: 'Foobar', fields: [ + Field( + name: 'field1', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(), root, sink); @@ -22,9 +26,13 @@ void main() { test('gen one class source', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'String')]), + Class(name: 'Foobar', fields: [ + Field( + name: 'field1', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink); @@ -76,8 +84,16 @@ void main() { Class( name: 'Foobar', fields: [ - Field(name: 'field1', dataType: 'String'), - Field(name: 'enum1', dataType: 'Enum1'), + Field( + name: 'field1', + dataType: 'String', + isNullable: true, + ), + Field( + name: 'enum1', + dataType: 'Enum1', + isNullable: true, + ), ], ), ], @@ -102,15 +118,27 @@ void main() { test('gen one api header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(), root, sink); @@ -125,15 +153,27 @@ void main() { test('gen one api source', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink); @@ -147,14 +187,46 @@ void main() { test('all the simple datatypes header', () { final Root root = Root(apis: [], classes: [ Class(name: 'Foobar', fields: [ - Field(name: 'aBool', dataType: 'bool'), - Field(name: 'aInt', dataType: 'int'), - Field(name: 'aDouble', dataType: 'double'), - Field(name: 'aString', dataType: 'String'), - Field(name: 'aUint8List', dataType: 'Uint8List'), - Field(name: 'aInt32List', dataType: 'Int32List'), - Field(name: 'aInt64List', dataType: 'Int64List'), - Field(name: 'aFloat64List', dataType: 'Float64List'), + Field( + name: 'aBool', + dataType: 'bool', + isNullable: true, + ), + Field( + name: 'aInt', + dataType: 'int', + isNullable: true, + ), + Field( + name: 'aDouble', + dataType: 'double', + isNullable: true, + ), + Field( + name: 'aString', + dataType: 'String', + isNullable: true, + ), + Field( + name: 'aUint8List', + dataType: 'Uint8List', + isNullable: true, + ), + Field( + name: 'aInt32List', + dataType: 'Int32List', + isNullable: true, + ), + Field( + name: 'aInt64List', + dataType: 'Int64List', + isNullable: true, + ), + Field( + name: 'aFloat64List', + dataType: 'Float64List', + isNullable: true, + ), ]), ], enums: []); @@ -180,7 +252,11 @@ void main() { test('bool source', () { final Root root = Root(apis: [], classes: [ Class(name: 'Foobar', fields: [ - Field(name: 'aBool', dataType: 'bool'), + Field( + name: 'aBool', + dataType: 'bool', + isNullable: true, + ), ]), ], enums: []); @@ -193,12 +269,20 @@ void main() { test('nested class header', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Nested', - fields: [Field(name: 'nested', dataType: 'Input')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Nested', fields: [ + Field( + name: 'nested', + dataType: 'Input', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(header: 'foo.h'), root, sink); @@ -209,12 +293,20 @@ void main() { test('nested class source', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Nested', - fields: [Field(name: 'nested', dataType: 'Input')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Nested', fields: [ + Field( + name: 'nested', + dataType: 'Input', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink); @@ -225,9 +317,13 @@ void main() { test('prefix class header', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'String')]), + Class(name: 'Foobar', fields: [ + Field( + name: 'field1', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(prefix: 'ABC'), root, sink); @@ -237,9 +333,13 @@ void main() { test('prefix class source', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'String')]), + Class(name: 'Foobar', fields: [ + Field( + name: 'field1', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource(const ObjcOptions(prefix: 'ABC'), root, sink); @@ -250,15 +350,27 @@ void main() { test('prefix nested class header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'Nested') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'Nested') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Nested', - fields: [Field(name: 'nested', dataType: 'Input')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Nested', fields: [ + Field( + name: 'nested', + dataType: 'Input', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(prefix: 'ABC'), root, sink); @@ -271,15 +383,27 @@ void main() { test('prefix nested class source', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'Nested') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'Nested') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Nested', - fields: [Field(name: 'nested', dataType: 'Input')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Nested', fields: [ + Field( + name: 'nested', + dataType: 'Input', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource(const ObjcOptions(prefix: 'ABC'), root, sink); @@ -292,15 +416,27 @@ void main() { test('gen flutter api header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.flutter, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(header: 'foo.h'), root, sink); @@ -316,15 +452,27 @@ void main() { test('gen flutter api source', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.flutter, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]) + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]) ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource(const ObjcOptions(header: 'foo.h'), root, sink); @@ -336,12 +484,20 @@ void main() { test('gen host void header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'void') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'void') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -353,12 +509,20 @@ void main() { test('gen host void source', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'void') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'void') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( @@ -372,12 +536,20 @@ void main() { test('gen flutter void return header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.flutter, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'void') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'void') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -389,12 +561,20 @@ void main() { test('gen flutter void return source', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.flutter, methods: [ - Method(name: 'doSomething', argType: 'Input', returnType: 'void') + Method( + name: 'doSomething', + argType: 'Input', + isArgNullable: false, + returnType: 'void') ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( @@ -407,12 +587,20 @@ void main() { test('gen host void arg header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'void', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'void', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -424,12 +612,20 @@ void main() { test('gen host void arg source', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.host, methods: [ - Method(name: 'doSomething', argType: 'void', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'void', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( @@ -441,12 +637,20 @@ void main() { test('gen flutter void arg header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.flutter, methods: [ - Method(name: 'doSomething', argType: 'void', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'void', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -461,12 +665,20 @@ void main() { test('gen flutter void arg header', () { final Root root = Root(apis: [ Api(name: 'Api', location: ApiLocation.flutter, methods: [ - Method(name: 'doSomething', argType: 'void', returnType: 'Output') + Method( + name: 'doSomething', + argType: 'void', + isArgNullable: false, + returnType: 'Output') ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( @@ -481,9 +693,13 @@ void main() { test('gen list', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'List')]), + Class(name: 'Foobar', fields: [ + Field( + name: 'field1', + dataType: 'List', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(), root, sink); @@ -494,9 +710,13 @@ void main() { test('gen map', () { final Root root = Root(apis: [], classes: [ - Class( - name: 'Foobar', - fields: [Field(name: 'field1', dataType: 'Map')]), + Class(name: 'Foobar', fields: [ + Field( + name: 'field1', + dataType: 'Map', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader(const ObjcOptions(), root, sink); @@ -511,16 +731,25 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: true) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -538,16 +767,25 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: true) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -565,13 +803,18 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: true) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcHeader( @@ -589,6 +832,7 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'void', isAsynchronous: true) ]) @@ -609,16 +853,25 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'Output', isAsynchronous: true) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( @@ -636,16 +889,25 @@ void main() { Method( name: 'doSomething', argType: 'Input', + isArgNullable: false, returnType: 'void', isAsynchronous: true) ]) ], classes: [ - Class( - name: 'Input', - fields: [Field(name: 'input', dataType: 'String')]), - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Input', fields: [ + Field( + name: 'input', + dataType: 'String', + isNullable: true, + ) + ]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( @@ -663,6 +925,7 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'void', isAsynchronous: true) ]) @@ -681,13 +944,18 @@ void main() { Method( name: 'doSomething', argType: 'void', + isArgNullable: false, returnType: 'Output', isAsynchronous: true) ]) ], classes: [ - Class( - name: 'Output', - fields: [Field(name: 'output', dataType: 'String')]), + Class(name: 'Output', fields: [ + Field( + name: 'output', + dataType: 'String', + isNullable: true, + ) + ]), ], enums: []); final StringBuffer sink = StringBuffer(); generateObjcSource( diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 7273139e7e..7054b3b00f 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -184,10 +184,12 @@ void main() { expect(input?.fields.length, equals(1)); expect(input?.fields[0].name, equals('input')); expect(input?.fields[0].dataType, equals('String')); + expect(input?.fields[0].isNullable, isTrue); expect(output?.fields.length, equals(1)); expect(output?.fields[0].name, equals('output')); expect(output?.fields[0].dataType, equals('String')); + expect(output?.fields[0].isNullable, isTrue); }); test('invalid datatype', () { @@ -208,6 +210,7 @@ void main() { expect(results.root.classes[0].name, equals('ClassWithEnum')); expect(results.root.classes[0].fields.length, equals(1)); expect(results.root.classes[0].fields[0].dataType, equals('Enum1')); + expect(results.root.classes[0].fields[0].isNullable, isTrue); expect(results.root.classes[0].fields[0].name, equals('enum1')); }); @@ -232,6 +235,7 @@ void main() { results.root.classes.firstWhere((Class x) => x.name == 'Nested'); expect(nested.fields.length, equals(1)); expect(nested.fields[0].dataType, equals('Input1')); + expect(nested.fields[0].isNullable, isTrue); }); test('flutter api', () { @@ -417,6 +421,161 @@ abstract class NotificationsHostApi { }); }); + test('test method in data class error', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +class Foo { + int? x; + int? foo() { return x; } +} + +@HostApi() +abstract class Api { + Foo doit(Foo foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 3); + expect(results.errors[0].message, contains('Method')); + }); + }); + + test('test field initialization', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +class Foo { + int? x = 123; +} + +@HostApi() +abstract class Api { + Foo doit(Foo foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 2); + expect(results.errors[0].message, contains('Initialization')); + }); + }); + + test('test field in api error', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +class Foo { + int? x; +} + +@HostApi() +abstract class Api { + int? x; + Foo doit(Foo foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 7); + expect(results.errors[0].message, contains('Field')); + }); + }); + + test('constructor in data class', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +class Foo { + int? x; + Foo(this.x); +} + +@HostApi() +abstract class Api { + Foo doit(Foo foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 3); + expect(results.errors[0].message, contains('Constructor')); + }); + }); + + test('nullable api arguments', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +class Foo { + int? x; +} + +@HostApi() +abstract class Api { + Foo doit(Foo? foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 7); + expect(results.errors[0].message, contains('Nullable')); + }); + }); + + test('nullable api return', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +class Foo { + int? x; +} + +@HostApi() +abstract class Api { + Foo? doit(Foo foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 7); + expect(results.errors[0].message, contains('Nullable')); + }); + }); + + test('primitive arguments', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +@HostApi() +abstract class Api { + void doit(int foo); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 3); + expect(results.errors[0].message, contains('Primitive')); + }); + }); + + test('primitive return', () { + final Pigeon dartle = Pigeon.setup(); + _withTempFile('compilationError.dart', (File file) { + file.writeAsStringSync(''' +@HostApi() +abstract class Api { + int doit(); +} +'''); + final ParseResults results = dartle.parseFile(file.path); + expect(results.errors.length, 1); + expect(results.errors[0].lineNumber, 3); + expect(results.errors[0].message, contains('Primitive')); + }); + }); + test('test invalid import', () { final Pigeon dartle = Pigeon.setup(); _withTempFile('compilationError.dart', (File file) {