diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md index 38f8fa7e..1d56341d 100644 --- a/protoc_plugin/CHANGELOG.md +++ b/protoc_plugin/CHANGELOG.md @@ -9,6 +9,9 @@ protobuf library. ([#503], [#907]) * Generate doc comments for enum types and values, rpc services and methods. ([#900], [#909]) +* `deprecated` options in messages, grpc services and methods, and enum types + and values are now handled to generate Dart `@deprecated` annotations. + ([#900], [#908]) [#738]: https://github.com/google/protobuf.dart/issues/738 [#903]: https://github.com/google/protobuf.dart/pull/903 @@ -16,6 +19,7 @@ [#907]: https://github.com/google/protobuf.dart/pull/907 [#900]: https://github.com/google/protobuf.dart/issues/900 [#909]: https://github.com/google/protobuf.dart/pull/909 +[#908]: https://github.com/google/protobuf.dart/pull/908 ## 21.1.2 diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile index 77dca0ba..0aa7a131 100644 --- a/protoc_plugin/Makefile +++ b/protoc_plugin/Makefile @@ -24,6 +24,7 @@ TEST_PROTO_LIST = \ google/protobuf/wrappers \ custom_option \ dart_name \ + deprecations \ doc_comments \ default_value_escape \ entity \ diff --git a/protoc_plugin/lib/src/client_generator.dart b/protoc_plugin/lib/src/client_generator.dart index 2f94b549..cc82735e 100644 --- a/protoc_plugin/lib/src/client_generator.dart +++ b/protoc_plugin/lib/src/client_generator.dart @@ -46,6 +46,10 @@ class ClientApiGenerator { if (commentBlock != null) { out.println(commentBlock); } + if (service._descriptor.options.deprecated) { + out.println( + '@$coreImportPrefix.Deprecated(\'This service is deprecated\')'); + } out.addBlock('class ${className}Api {', '}', () { out.println('$_clientType _client;'); out.println('${className}Api(this._client);'); @@ -73,6 +77,10 @@ class ClientApiGenerator { if (commentBlock != null) { out.println(commentBlock); } + if (m.options.deprecated) { + out.println( + '@$coreImportPrefix.Deprecated(\'This method is deprecated\')'); + } out.addBlock( '$asyncImportPrefix.Future<$outputType> $methodName(' '$protobufImportPrefix.ClientContext? ctx, $inputType request) =>', diff --git a/protoc_plugin/lib/src/enum_generator.dart b/protoc_plugin/lib/src/enum_generator.dart index 145b8e80..875e3aed 100644 --- a/protoc_plugin/lib/src/enum_generator.dart +++ b/protoc_plugin/lib/src/enum_generator.dart @@ -106,6 +106,9 @@ class EnumGenerator extends ProtobufContainer { if (commentBlock != null) { out.println(commentBlock); } + if (_descriptor.options.deprecated) { + out.println('@$coreImportPrefix.Deprecated(\'This enum is deprecated\')'); + } out.addAnnotatedBlock( 'class $classname extends $protobufImportPrefix.ProtobufEnum {', '}\n', [ @@ -129,6 +132,11 @@ class EnumGenerator extends ProtobufContainer { out.println(commentBlock); } + if (val.options.deprecated) { + out.println( + '@$coreImportPrefix.Deprecated(\'This enum value is deprecated\')'); + } + out.printlnAnnotated( 'static const $classname $name = ' '$classname._(${val.number}, $conditionalValName);', diff --git a/protoc_plugin/lib/src/grpc_generator.dart b/protoc_plugin/lib/src/grpc_generator.dart index 219a10e5..385c15aa 100644 --- a/protoc_plugin/lib/src/grpc_generator.dart +++ b/protoc_plugin/lib/src/grpc_generator.dart @@ -106,6 +106,10 @@ class GrpcServiceGenerator { } void _generateClient(IndentingWriter out) { + if (_descriptor.options.deprecated) { + out.println( + '@$coreImportPrefix.Deprecated(\'This service is deprecated\')'); + } out.println( '@$protobufImportPrefix.GrpcServiceName(\'$_fullServiceName\')'); out.addBlock('class $_clientClassname extends $_client {', '}', () { @@ -170,6 +174,8 @@ class _GrpcMethod { final String _clientReturnType; final String _serverReturnType; + final bool _deprecated; + _GrpcMethod._( this._grpcName, this._dartName, @@ -180,7 +186,8 @@ class _GrpcMethod { this._responseType, this._argumentType, this._clientReturnType, - this._serverReturnType); + this._serverReturnType, + this._deprecated); factory _GrpcMethod(GrpcServiceGenerator service, GenerationContext ctx, MethodDescriptorProto method) { @@ -204,6 +211,8 @@ class _GrpcMethod { final serverReturnType = serverStreaming ? '$_stream<$responseType>' : '$_future<$responseType>'; + final deprecated = method.options.deprecated; + return _GrpcMethod._( grpcName, dartName, @@ -214,7 +223,8 @@ class _GrpcMethod { responseType, argumentType, clientReturnType, - serverReturnType); + serverReturnType, + deprecated); } void generateClientMethodDescriptor(IndentingWriter out) { @@ -228,6 +238,10 @@ class _GrpcMethod { void generateClientStub(IndentingWriter out) { out.println(); + if (_deprecated) { + out.println( + '@$coreImportPrefix.Deprecated(\'This method is deprecated\')'); + } out.addBlock( '$_clientReturnType $_dartName($_argumentType request, {${GrpcServiceGenerator._callOptions}? options}) {', '}', () { diff --git a/protoc_plugin/lib/src/message_generator.dart b/protoc_plugin/lib/src/message_generator.dart index bd04d797..ee6e099f 100644 --- a/protoc_plugin/lib/src/message_generator.dart +++ b/protoc_plugin/lib/src/message_generator.dart @@ -327,6 +327,10 @@ class MessageGenerator extends ProtobufContainer { if (commentBlock != null) { out.println(commentBlock); } + if (_descriptor.options.deprecated) { + out.println( + '@$coreImportPrefix.Deprecated(\'This message is deprecated\')'); + } out.addAnnotatedBlock( 'class $classname extends $protobufImportPrefix.$extendedClass$mixinClause {', '}', [ diff --git a/protoc_plugin/test/deprecations_test.dart b/protoc_plugin/test/deprecations_test.dart new file mode 100644 index 00000000..3b386c79 --- /dev/null +++ b/protoc_plugin/test/deprecations_test.dart @@ -0,0 +1,22 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. 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:test/test.dart'; + +import 'golden_file.dart'; + +void main() { + test('Deprecated annotation generation for messages', () { + final actual = File('out/protos/deprecations.pb.dart').readAsStringSync(); + expectMatchesGoldenFile(actual, 'test/goldens/deprecations'); + }); + + test('Deprecated annotation generation for enums', () { + final actual = File('out/protos/constructor_args/deprecations.pbenum.dart') + .readAsStringSync(); + expectMatchesGoldenFile(actual, 'test/goldens/deprecations.pbenum'); + }); +} diff --git a/protoc_plugin/test/goldens/deprecations b/protoc_plugin/test/goldens/deprecations new file mode 100644 index 00000000..9a03a713 --- /dev/null +++ b/protoc_plugin/test/goldens/deprecations @@ -0,0 +1,144 @@ +// +// Generated code. Do not modify. +// source: deprecations.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'deprecations.pbenum.dart'; + +@$core.Deprecated('This message is deprecated') +class HelloRequest extends $pb.GeneratedMessage { + factory HelloRequest() => create(); + HelloRequest._() : super(); + factory HelloRequest.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory HelloRequest.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HelloRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'service2'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'name') + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + HelloRequest clone() => HelloRequest()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + HelloRequest copyWith(void Function(HelloRequest) updates) => + super.copyWith((message) => updates(message as HelloRequest)) + as HelloRequest; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static HelloRequest create() => HelloRequest._(); + HelloRequest createEmptyInstance() => create(); + static $pb.PbList createRepeated() => + $pb.PbList(); + @$core.pragma('dart2js:noInline') + static HelloRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static HelloRequest? _defaultInstance; + + @$core.Deprecated('This field is deprecated.') + @$pb.TagNumber(1) + $core.String get name => $_getSZ(0); + @$core.Deprecated('This field is deprecated.') + @$pb.TagNumber(1) + set name($core.String v) { + $_setString(0, v); + } + + @$core.Deprecated('This field is deprecated.') + @$pb.TagNumber(1) + $core.bool hasName() => $_has(0); + @$core.Deprecated('This field is deprecated.') + @$pb.TagNumber(1) + void clearName() => clearField(1); +} + +class HelloReply extends $pb.GeneratedMessage { + factory HelloReply() => create(); + HelloReply._() : super(); + factory HelloReply.fromBuffer($core.List<$core.int> i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(i, r); + factory HelloReply.fromJson($core.String i, + [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HelloReply', + package: const $pb.PackageName(_omitMessageNames ? '' : 'service2'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'message') + ..hasRequiredFields = false; + + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.deepCopy] instead. ' + 'Will be removed in next major version') + HelloReply clone() => HelloReply()..mergeFromMessage(this); + @$core.Deprecated('Using this can add significant overhead to your binary. ' + 'Use [GeneratedMessageGenericExtensions.rebuild] instead. ' + 'Will be removed in next major version') + HelloReply copyWith(void Function(HelloReply) updates) => + super.copyWith((message) => updates(message as HelloReply)) as HelloReply; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static HelloReply create() => HelloReply._(); + HelloReply createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static HelloReply getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static HelloReply? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get message => $_getSZ(0); + @$pb.TagNumber(1) + set message($core.String v) { + $_setString(0, v); + } + + @$pb.TagNumber(1) + $core.bool hasMessage() => $_has(0); + @$pb.TagNumber(1) + void clearMessage() => clearField(1); +} + +@$core.Deprecated('This service is deprecated') +class GreeterApi { + $pb.RpcClient _client; + GreeterApi(this._client); + + @$core.Deprecated('This method is deprecated') + $async.Future sayHello( + $pb.ClientContext? ctx, HelloRequest request) => + _client.invoke( + ctx, 'Greeter', 'SayHello', request, HelloReply()); +} + +const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); +const _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/protoc_plugin/test/goldens/deprecations.pbenum b/protoc_plugin/test/goldens/deprecations.pbenum new file mode 100644 index 00000000..235430d9 --- /dev/null +++ b/protoc_plugin/test/goldens/deprecations.pbenum @@ -0,0 +1,34 @@ +// +// Generated code. Do not modify. +// source: deprecations.proto +// +// @dart = 2.12 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_final_fields +// ignore_for_file: unnecessary_import, unnecessary_this, unused_import + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +@$core.Deprecated('This enum is deprecated') +class A extends $pb.ProtobufEnum { + @$core.Deprecated('This enum value is deprecated') + static const A A1 = A._(0, _omitEnumNames ? '' : 'A1'); + static const A A2 = A._(1, _omitEnumNames ? '' : 'A2'); + + static const $core.List values = [ + A1, + A2, + ]; + + static final $core.Map<$core.int, A> _byValue = + $pb.ProtobufEnum.initByValue(values); + static A? valueOf($core.int value) => _byValue[value]; + + const A._($core.int v, $core.String n) : super(v, n); +} + +const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/protoc_plugin/test/goldens/doc_comments b/protoc_plugin/test/goldens/doc_comments index 406ef045..e7964eeb 100644 --- a/protoc_plugin/test/goldens/doc_comments +++ b/protoc_plugin/test/goldens/doc_comments @@ -14,6 +14,8 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + export 'doc_comments.pbenum.dart'; /// This is a message. @@ -29,7 +31,7 @@ class HelloRequest extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo( _omitMessageNames ? '' : 'HelloRequest', - package: const $pb.PackageName(_omitMessageNames ? '' : 'service'), + package: const $pb.PackageName(_omitMessageNames ? '' : 'service1'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'name') ..hasRequiredFields = false; @@ -83,7 +85,7 @@ class HelloReply extends $pb.GeneratedMessage { static final $pb.BuilderInfo _i = $pb.BuilderInfo( _omitMessageNames ? '' : 'HelloReply', - package: const $pb.PackageName(_omitMessageNames ? '' : 'service'), + package: const $pb.PackageName(_omitMessageNames ? '' : 'service1'), createEmptyInstance: create) ..aOS(1, _omitFieldNames ? '' : 'message') ..hasRequiredFields = false; diff --git a/protoc_plugin/test/protos/deprecations.proto b/protoc_plugin/test/protos/deprecations.proto new file mode 100644 index 00000000..fc4d8ae0 --- /dev/null +++ b/protoc_plugin/test/protos/deprecations.proto @@ -0,0 +1,32 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +syntax = "proto3"; + +package service2; + +service Greeter { + option deprecated = true; + + rpc SayHello (HelloRequest) returns (HelloReply) { + option deprecated = true; + } +} + +message HelloRequest { + option deprecated = true; + + string name = 1 [deprecated = true]; +} + +message HelloReply { + string message = 1; +} + +enum A { + option deprecated = true; + + A1 = 0 [deprecated = true]; + A2 = 1; +} diff --git a/protoc_plugin/test/protos/doc_comments.proto b/protoc_plugin/test/protos/doc_comments.proto index e4021c5f..2c7d87f9 100644 --- a/protoc_plugin/test/protos/doc_comments.proto +++ b/protoc_plugin/test/protos/doc_comments.proto @@ -4,7 +4,7 @@ syntax = "proto3"; -package service; +package service1; // This is a service. service Greeter {