From f840c247beedee26cd8d9de26a3725c175a98cd1 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 29 Jan 2024 09:40:19 +0000 Subject: [PATCH] feat: Add the Obsolete attribute to all types generated for a deprecated service --- .../Roslyn/RoslynExtensions.cs | 16 ++++++++++++++++ .../ServiceAbstractClientClassCodeGenerator.cs | 1 + .../Generation/ServiceBuilderCodeGenerator.cs | 3 ++- .../ServiceCollectionExtensionsGenerator.cs | 1 + .../Generation/ServiceDetails.cs | 6 ++++++ .../ServiceImplClientClassGenerator.cs | 1 + .../Generation/ServiceSettingsCodeGenerator.cs | 1 + .../Generation/SnippetCodeGenerator.cs | 4 ++++ 8 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Google.Api.Generator.Utils/Roslyn/RoslynExtensions.cs b/Google.Api.Generator.Utils/Roslyn/RoslynExtensions.cs index 4b14ab29..ece39422 100644 --- a/Google.Api.Generator.Utils/Roslyn/RoslynExtensions.cs +++ b/Google.Api.Generator.Utils/Roslyn/RoslynExtensions.cs @@ -352,6 +352,9 @@ public static PropertyDeclarationSyntax WithAttribute(this PropertyDeclarationSy public static EnumMemberDeclarationSyntax WithAttribute(this EnumMemberDeclarationSyntax enumDeclaration, AttributeSyntax attr) => enumDeclaration.WithAttributeLists(enumDeclaration.AttributeLists.Add(AttributeList(SingletonSeparatedList(attr)))); + public static ClassDeclarationSyntax WithAttribute(this ClassDeclarationSyntax classDeclaration, AttributeSyntax attr) => + classDeclaration.WithAttributeLists(classDeclaration.AttributeLists.Add(AttributeList(SingletonSeparatedList(attr)))); + public static RoslynBuilder.ArgumentsFunc WithAttribute(this MethodDeclarationSyntax method, TypeSyntax attrType) => args => method.WithAttribute(RoslynBuilder.AttributeWithArgs(attrType, args)); @@ -361,6 +364,9 @@ public static RoslynBuilder.ArgumentsFunc WithAttribu public static RoslynBuilder.ArgumentsFunc WithAttribute(this EnumMemberDeclarationSyntax enumDeclaration, TypeSyntax attrType) => args => enumDeclaration.WithAttribute(RoslynBuilder.AttributeWithArgs(attrType, args)); + public static RoslynBuilder.ArgumentsFunc WithAttribute(this ClassDeclarationSyntax classDeclaration, TypeSyntax attrType) => + args => classDeclaration.WithAttribute(RoslynBuilder.AttributeWithArgs(attrType, args)); + /// /// Returns the specified method declaration syntax, potentially (if is true) adding an attribute specified /// by . This is a function so that if obtaining the attribute type has side-effects, @@ -381,6 +387,16 @@ public static RoslynBuilder.ArgumentsFunc MaybeWithAt ? args => property.WithAttribute(RoslynBuilder.AttributeWithArgs(attrType(), args)) : args => property; + /// + /// Returns the specified property declaration syntax, potentially (if is true) adding an attribute specified + /// by . This is a function so that if obtaining the attribute type has side-effects, + /// those side effects will not have an impact unless the attribute is added. + /// + public static RoslynBuilder.ArgumentsFunc MaybeWithAttribute(this ClassDeclarationSyntax clazz, bool condition, Func attrType) => + condition + ? args => clazz.WithAttribute(RoslynBuilder.AttributeWithArgs(attrType(), args)) + : args => clazz; + public static BinaryExpressionSyntax Is(this ExpressionSyntax expr, TypeSyntax type) => BinaryExpression(SyntaxKind.IsExpression, expr, type); public static BinaryExpressionSyntax Is(this ParameterSyntax expr, TypeSyntax type) => IdentifierName(expr.Identifier).Is(type); diff --git a/Google.Api.Generator/Generation/ServiceAbstractClientClassCodeGenerator.cs b/Google.Api.Generator/Generation/ServiceAbstractClientClassCodeGenerator.cs index c03abd9c..ed1a6ca9 100644 --- a/Google.Api.Generator/Generation/ServiceAbstractClientClassCodeGenerator.cs +++ b/Google.Api.Generator/Generation/ServiceAbstractClientClassCodeGenerator.cs @@ -47,6 +47,7 @@ private ServiceAbstractClientClassCodeGenerator(SourceFileContext ctx, ServiceDe private ClassDeclarationSyntax Generate() { var cls = Class(Public | Abstract | Partial, _svc.ClientAbstractTyp) + .MaybeWithAttribute(_svc.IsDeprecated, () => _ctx.Type())() .WithXmlDoc( XmlDoc.Summary($"{_svc.DocumentationName} client wrapper, for convenient use."), XmlDoc.RemarksPreFormatted(_svc.DocLines)); diff --git a/Google.Api.Generator/Generation/ServiceBuilderCodeGenerator.cs b/Google.Api.Generator/Generation/ServiceBuilderCodeGenerator.cs index 0d76b360..cdfb8e12 100644 --- a/Google.Api.Generator/Generation/ServiceBuilderCodeGenerator.cs +++ b/Google.Api.Generator/Generation/ServiceBuilderCodeGenerator.cs @@ -18,7 +18,7 @@ using Grpc.Core; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Extensions.Logging; -using System.Collections.Generic; +using System; using System.Threading; using System.Threading.Tasks; using static Google.Api.Generator.Utils.Roslyn.Modifier; @@ -45,6 +45,7 @@ private ClassDeclarationSyntax Generate() { var baseTyp = Typ.Generic(typeof(ClientBuilderBase<>), _svc.ClientAbstractTyp); var cls = Class(Public | Sealed | Partial, _svc.BuilderTyp, baseTypes: _ctx.Type(baseTyp)) + .MaybeWithAttribute(_svc.IsDeprecated, () => _ctx.Type())() .WithXmlDoc(XmlDoc.Summary("Builder class for ", _ctx.Type(_svc.ClientAbstractTyp), " to provide simple configuration of credentials, endpoint etc.")); using (_ctx.InClass(cls)) { diff --git a/Google.Api.Generator/Generation/ServiceCollectionExtensionsGenerator.cs b/Google.Api.Generator/Generation/ServiceCollectionExtensionsGenerator.cs index de9206ea..a60e8e89 100644 --- a/Google.Api.Generator/Generation/ServiceCollectionExtensionsGenerator.cs +++ b/Google.Api.Generator/Generation/ServiceCollectionExtensionsGenerator.cs @@ -71,6 +71,7 @@ private static MethodDeclarationSyntax GenerateMethod(SourceFileContext ctx, Ser ); return Method(Public | Static, serviceCollection, name)(services, action) + .MaybeWithAttribute(service.IsDeprecated, () => ctx.Type())() .WithBody(services.Call("AddSingleton")(lambda)) .WithXmlDoc( XmlDoc.Summary("Adds a singleton ", ctx.Type(service.ClientAbstractTyp), " to ", services, "."), diff --git a/Google.Api.Generator/Generation/ServiceDetails.cs b/Google.Api.Generator/Generation/ServiceDetails.cs index 0c95fb3d..a8d4666c 100644 --- a/Google.Api.Generator/Generation/ServiceDetails.cs +++ b/Google.Api.Generator/Generation/ServiceDetails.cs @@ -101,6 +101,7 @@ public ServiceDetails(ProtoCatalog catalog, string ns, ServiceDescriptor desc, S .Where(mixin => mixin.GapicClientType.Namespace != ns) .ToList() ?? Enumerable.Empty(); Transports = transports; + IsDeprecated = desc.GetOptions()?.Deprecated == true; } /// The lines of service documentation from the proto. @@ -204,6 +205,11 @@ public ServiceDetails(ProtoCatalog catalog, string ns, ServiceDescriptor desc, S /// public ClientLibrarySettings LibrarySettings { get; } + /// + /// Whether the service itself is deprecated. + /// + public bool IsDeprecated { get; } + /// /// The details of a service responsible for LRO polling for a non-standard LRO implementation. /// (Multiple other services may have methods that return operations polled by the service.) diff --git a/Google.Api.Generator/Generation/ServiceImplClientClassGenerator.cs b/Google.Api.Generator/Generation/ServiceImplClientClassGenerator.cs index b1dc75c2..b6348253 100644 --- a/Google.Api.Generator/Generation/ServiceImplClientClassGenerator.cs +++ b/Google.Api.Generator/Generation/ServiceImplClientClassGenerator.cs @@ -69,6 +69,7 @@ private ServiceImplClientClassGenerator(SourceFileContext ctx, ServiceDetails sv private ClassDeclarationSyntax Generate() { var cls = Class(Public | Sealed | Partial, _svc.ClientImplTyp, baseTypes: _ctx.Type(_svc.ClientAbstractTyp)) + .MaybeWithAttribute(_svc.IsDeprecated, () => _ctx.Type())() .WithXmlDoc( XmlDoc.Summary($"{_svc.DocumentationName} client wrapper implementation, for convenient use."), XmlDoc.RemarksPreFormatted(_svc.DocLines)); diff --git a/Google.Api.Generator/Generation/ServiceSettingsCodeGenerator.cs b/Google.Api.Generator/Generation/ServiceSettingsCodeGenerator.cs index f5afb113..1f6ece38 100644 --- a/Google.Api.Generator/Generation/ServiceSettingsCodeGenerator.cs +++ b/Google.Api.Generator/Generation/ServiceSettingsCodeGenerator.cs @@ -53,6 +53,7 @@ private ServiceSettingsCodeGenerator(SourceFileContext ctx, ServiceDetails svc) private ClassDeclarationSyntax Generate() { var cls = Class(Public | Sealed | Partial, _svc.SettingsTyp, baseTypes: _ctx.Type()) + .MaybeWithAttribute(_svc.IsDeprecated, () => _ctx.Type())() .WithXmlDoc(XmlDoc.Summary("Settings for ", _ctx.Type(_svc.ClientAbstractTyp), " instances.")); using (_ctx.InClass(cls)) { diff --git a/Google.Api.Generator/Generation/SnippetCodeGenerator.cs b/Google.Api.Generator/Generation/SnippetCodeGenerator.cs index 15ec33ad..473ff21b 100644 --- a/Google.Api.Generator/Generation/SnippetCodeGenerator.cs +++ b/Google.Api.Generator/Generation/SnippetCodeGenerator.cs @@ -67,6 +67,7 @@ public static CompilationUnitSyntax Generate(SourceFileContext ctx, ServiceDetai using (ctx.InNamespace(ns)) { var cls = Class(Public | Sealed, svc.ServiceSnippetsTyp) + .MaybeWithAttribute(svc.IsDeprecated, () => ctx.Type())() .WithXmlDoc(XmlDoc.Summary("Generated snippets.")); using (ctx.InClass(cls)) { @@ -237,12 +238,15 @@ private SnippetMetadata GenerateMetadata() => var ns = Namespace(TargetMethod.Svc.SnippetsNamespace); using (ctx.InNamespace(ns)) { + // We can't apply the Obsolete attribute multiple times on a class, so instead + // we apply it to each snippet. var cls = Class(Public | Sealed | Partial, TargetMethod.Svc.SnippetsTyp); using (ctx.InClass(cls)) { cls = cls.AddMembers( // Do not include doc markers. Full snippets use region tags and metadata. MethodGenerator(ctx, false) + .MaybeWithAttribute(TargetMethod.Svc.IsDeprecated, () => ctx.Type())() .WithXmlDoc( XmlDoc.Summary($"Snippet for {TargetMethodName}"), XmlDoc.RemarksPreFormatted(GeneratedNotice)));