diff --git a/analysis_options.yaml b/analysis_options.yaml index 02f94ca6d7..69b3fabcbc 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -38,7 +38,7 @@ linter: - always_specify_types - annotate_overrides # - avoid_annotating_with_dynamic # conflicts with always_specify_types - # - avoid_as # conflicts with NNBD + # - avoid_as # no longer relevant with null safety - avoid_bool_literals_in_conditional_expressions # - avoid_catches_without_on_clauses # we do this commonly # - avoid_catching_errors # we do this commonly diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index dd12d1e06c..afecc10659 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.19 + +* Fixed a bug introduced in 0.1.17 where methods without arguments were + no longer being called. + ## 0.1.18 * Null safe requires Dart 2.12. diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index ac4333e48a..1e3acb4b55 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -88,6 +88,7 @@ void _writeFlutterApi( }) { assert(api.location == ApiLocation.flutter); final String nullTag = opt.isNullSafe ? '?' : ''; + final String unwrapOperator = opt.isNullSafe ? '!' : ''; indent.write('abstract class ${api.name} '); indent.scoped('{', '}', () { for (Method func in api.methods) { @@ -106,10 +107,10 @@ void _writeFlutterApi( indent.writeln( 'const BasicMessageChannel channel =', ); + final String channelName = channelNameFunc == null + ? makeChannelName(api, func) + : channelNameFunc(func); indent.nest(2, () { - final String channelName = channelNameFunc == null - ? makeChannelName(api, func) - : channelNameFunc(func); indent.writeln( 'BasicMessageChannel(\'$channelName\', StandardMessageCodec());', ); @@ -134,16 +135,16 @@ void _writeFlutterApi( : func.returnType == 'void' ? 'return;' : 'return null;'; - indent.write('if (message == null) '); - indent.scoped('{', '}', () { - indent.writeln(emptyReturnStatement); - }); String call; if (argType == 'void') { + indent.writeln('// ignore message'); call = 'api.${func.name}()'; } else { indent.writeln( - 'final $argType input = $argType.decode(message);', + 'assert(message != null, \'Argument for $channelName was null. Expected $argType.\');', + ); + indent.writeln( + 'final $argType input = $argType.decode(message$unwrapOperator);', ); call = 'api.${func.name}(input)'; } @@ -213,7 +214,6 @@ void generateDart(DartOptions opt, Root root, StringSink sink) { if (klass.fields.isNotEmpty) { indent.writeln(''); } - indent.writeln('// ignore: unused_element'); indent.write('Object encode() '); indent.scoped('{', '}', () { indent.writeln( @@ -232,7 +232,6 @@ void generateDart(DartOptions opt, Root root, StringSink sink) { indent.writeln('return pigeonMap;'); }); indent.writeln(''); - indent.writeln('// ignore: unused_element'); indent.write( 'static ${klass.name} decode(Object message) ', ); diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index bbb76e2b74..0e948a2eaa 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -7,8 +7,8 @@ import 'dart:io'; import 'dart:mirrors'; import 'ast.dart'; -/// The current version of pigeon. -const String pigeonVersion = '0.1.18'; +/// The current version of pigeon. This must match the version in pubspec.yaml. +const String pigeonVersion = '0.1.19'; /// Read all the content from [stdin] to a String. String readStdin() { diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart index 2b051a3e97..b7b97ff0c3 100644 --- a/packages/pigeon/mock_handler_tester/test/message.dart +++ b/packages/pigeon/mock_handler_tester/test/message.dart @@ -1,16 +1,16 @@ -// Autogenerated from Pigeon (v0.1.15), do not edit directly. +// Autogenerated from Pigeon (v0.1.19), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import // @dart = 2.8 import 'dart:async'; import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List; + import 'package:flutter/services.dart'; class SearchReply { String result; String error; - // ignore: unused_element Object encode() { final Map pigeonMap = {}; pigeonMap['result'] = result; @@ -18,7 +18,6 @@ class SearchReply { return pigeonMap; } - // ignore: unused_element static SearchReply decode(Object message) { final Map pigeonMap = message as Map; return SearchReply() @@ -32,7 +31,6 @@ class SearchRequest { int anInt; bool aBool; - // ignore: unused_element Object encode() { final Map pigeonMap = {}; pigeonMap['query'] = query; @@ -41,7 +39,6 @@ class SearchRequest { return pigeonMap; } - // ignore: unused_element static SearchRequest decode(Object message) { final Map pigeonMap = message as Map; return SearchRequest() @@ -54,14 +51,12 @@ class SearchRequest { class Nested { SearchRequest request; - // ignore: unused_element Object encode() { final Map pigeonMap = {}; pigeonMap['request'] = request == null ? null : request.encode(); return pigeonMap; } - // ignore: unused_element static Nested decode(Object message) { final Map pigeonMap = message as Map; return Nested() @@ -81,9 +76,8 @@ abstract class FlutterSearchApi { channel.setMessageHandler(null); } else { channel.setMessageHandler((Object message) async { - if (message == null) { - return null; - } + assert(message != null, + 'Argument for dev.flutter.pigeon.FlutterSearchApi.search was null. Expected SearchRequest.'); final SearchRequest input = SearchRequest.decode(message); final SearchReply output = api.search(input); return output.encode(); @@ -121,6 +115,30 @@ class NestedApi { } class Api { + Future initialize() async { + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Api.initialize', StandardMessageCodec()); + final Map replyMap = + await channel.send(null) as Map; + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null, + ); + } else if (replyMap['error'] != null) { + final Map error = + replyMap['error'] as Map; + throw PlatformException( + code: error['code'] as String, + message: error['message'] as String, + details: error['details'], + ); + } else { + // noop + } + } + Future search(SearchRequest arg) async { final Object encoded = arg.encode(); const BasicMessageChannel channel = BasicMessageChannel( diff --git a/packages/pigeon/mock_handler_tester/test/test.dart b/packages/pigeon/mock_handler_tester/test/test.dart index c2dc28e798..7c6ccf03de 100644 --- a/packages/pigeon/mock_handler_tester/test/test.dart +++ b/packages/pigeon/mock_handler_tester/test/test.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.15), do not edit directly. +// Autogenerated from Pigeon (v0.1.19), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import // @dart = 2.8 @@ -19,9 +19,8 @@ abstract class TestNestedApi { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object message) async { - if (message == null) { - return {}; - } + assert(message != null, + 'Argument for dev.flutter.pigeon.NestedApi.search was null. Expected Nested.'); final Nested input = Nested.decode(message); final SearchReply output = api.search(input); return {'result': output.encode()}; @@ -32,8 +31,22 @@ abstract class TestNestedApi { } abstract class TestHostApi { + void initialize(); SearchReply search(SearchRequest arg); static void setup(TestHostApi api) { + { + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Api.initialize', StandardMessageCodec()); + if (api == null) { + channel.setMockMessageHandler(null); + } else { + channel.setMockMessageHandler((Object message) async { + // ignore message + api.initialize(); + return {}; + }); + } + } { const BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.Api.search', StandardMessageCodec()); @@ -41,9 +54,8 @@ abstract class TestHostApi { channel.setMockMessageHandler(null); } else { channel.setMockMessageHandler((Object message) async { - if (message == null) { - return {}; - } + assert(message != null, + 'Argument for dev.flutter.pigeon.Api.search was null. Expected SearchRequest.'); final SearchRequest input = SearchRequest.decode(message); final SearchReply output = api.search(input); return {'result': output.encode()}; diff --git a/packages/pigeon/mock_handler_tester/test/widget_test.dart b/packages/pigeon/mock_handler_tester/test/widget_test.dart index 7fba290235..4f6497c45b 100644 --- a/packages/pigeon/mock_handler_tester/test/widget_test.dart +++ b/packages/pigeon/mock_handler_tester/test/widget_test.dart @@ -2,16 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'message.dart'; import 'test.dart'; class Mock implements TestHostApi { - bool didCall = false; + List log = []; + + @override + void initialize() { + log.add('initialize'); + } + @override SearchReply search(SearchRequest arg) { - didCall = true; + log.add('search'); return SearchReply()..result = arg.query; } } @@ -46,7 +55,48 @@ void main() { final Mock mock = Mock(); TestHostApi.setup(mock); final SearchReply reply = await api.search(SearchRequest()..query = 'foo'); - expect(mock.didCall, true); + expect(mock.log, ['search']); expect(reply.result, 'foo'); }); + + test('no-arg calls', () async { + final Api api = Api(); + final Mock mock = Mock(); + TestHostApi.setup(mock); + await api.initialize(); + expect(mock.log, ['initialize']); + }); + + test( + 'calling methods with null', + () async { + final Mock mock = Mock(); + TestHostApi.setup(mock); + expect( + await const BasicMessageChannel( + 'dev.flutter.pigeon.Api.initialize', + StandardMessageCodec(), + ).send(null), + isEmpty, + ); + try { + await const BasicMessageChannel( + 'dev.flutter.pigeon.Api.search', + StandardMessageCodec(), + ).send(null) as Map; + expect(true, isFalse); // should not reach here + } catch (error) { + expect(error, isAssertionError); + expect( + error.toString(), + contains( + 'Argument for dev.flutter.pigeon.Api.search was null. Expected SearchRequest.', + ), + ); + } + expect(mock.log, ['initialize']); + }, + // TODO(ianh): skip can be removed after first stable release in 2021 + skip: Platform.environment['CHANNEL'] == 'stable', + ); } diff --git a/packages/pigeon/pigeons/message.dart b/packages/pigeon/pigeons/message.dart index 2dff2f090e..02b95840f6 100644 --- a/packages/pigeon/pigeons/message.dart +++ b/packages/pigeon/pigeons/message.dart @@ -20,6 +20,7 @@ class SearchReply { @HostApi(dartHostTestHandler: 'TestHostApi') abstract class Api { + void initialize(); SearchReply search(SearchRequest request); } diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index 3d2425bc6a..1fd88de9f9 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -1,5 +1,5 @@ name: pigeon -version: 0.1.18 +version: 0.1.19 # This must match the version in lib/generator_tools.dart description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. homepage: https://github.com/flutter/packages/tree/master/packages/pigeon dependencies: diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 075e575bc6..2192a9a8ca 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -144,7 +144,10 @@ void main() { final StringBuffer sink = StringBuffer(); generateDart(DartOptions(), root, sink); final String code = sink.toString(); - expect(code, isNot(matches('=.*doSomething'))); + // The next line verifies that we're not setting a variable to the value of "doSomething", but + // ignores the line where we assert the value of the argument isn't null, since on that line + // we mention "doSomething" in the assertion message. + expect(code, isNot(matches('[^!]=.*doSomething'))); expect(code, contains('doSomething(')); expect(code, isNot(contains('.encode()'))); }); diff --git a/packages/pigeon/test/version_test.dart b/packages/pigeon/test/version_test.dart index 70e4382a12..d8c867b509 100644 --- a/packages/pigeon/test/version_test.dart +++ b/packages/pigeon/test/version_test.dart @@ -11,7 +11,7 @@ void main() { test('pigeon version matches pubspec', () { final String pubspecPath = '${Directory.current.path}/pubspec.yaml'; final String pubspec = File(pubspecPath).readAsStringSync(); - final RegExp regex = RegExp('version:\s*(.*)'); + final RegExp regex = RegExp('version:\s*(.*?) #'); final RegExpMatch match = regex.firstMatch(pubspec); expect(match, isNotNull); expect(pigeonVersion, match.group(1).trim());