88using Microsoft . CodeAnalysis . CSharp ;
99using Microsoft . AspNetCore . OpenApi . SourceGenerators . Xml ;
1010using System . Threading ;
11+ using System . Linq ;
1112
1213namespace Microsoft . AspNetCore . OpenApi . SourceGenerators ;
1314
@@ -74,28 +75,149 @@ file record XmlComment(
7475 {{ GeneratedCodeAttribute }}
7576 file record XmlResponseComment(string Code, string? Description, string? Example);
7677
78+ {{ GeneratedCodeAttribute }}
79+ file sealed record MemberKey(
80+ Type? DeclaringType,
81+ MemberType MemberKind,
82+ string? Name,
83+ Type? ReturnType,
84+ Type[]? Parameters) : IEquatable<MemberKey>
85+ {
86+ public bool Equals(MemberKey? other)
87+ {
88+ if (other is null) return false;
89+
90+ // Check member kind
91+ if (MemberKind != other.MemberKind) return false;
92+
93+ // Check declaring type, handling generic types
94+ if (!TypesEqual(DeclaringType, other.DeclaringType)) return false;
95+
96+ // Check name
97+ if (Name != other.Name) return false;
98+
99+ // For methods, check return type and parameters
100+ if (MemberKind == MemberType.Method)
101+ {
102+ if (!TypesEqual(ReturnType, other.ReturnType)) return false;
103+ if (Parameters is null || other.Parameters is null) return false;
104+ if (Parameters.Length != other.Parameters.Length) return false;
105+
106+ for (int i = 0; i < Parameters.Length; i++)
107+ {
108+ if (!TypesEqual(Parameters[i], other.Parameters[i])) return false;
109+ }
110+ }
111+
112+ return true;
113+ }
114+
115+ private static bool TypesEqual(Type? type1, Type? type2)
116+ {
117+ if (type1 == type2) return true;
118+ if (type1 == null || type2 == null) return false;
119+
120+ if (type1.IsGenericType && type2.IsGenericType)
121+ {
122+ return type1.GetGenericTypeDefinition() == type2.GetGenericTypeDefinition();
123+ }
124+
125+ return type1 == type2;
126+ }
127+
128+ public override int GetHashCode()
129+ {
130+ var hash = new HashCode();
131+ hash.Add(GetTypeHashCode(DeclaringType));
132+ hash.Add(MemberKind);
133+ hash.Add(Name);
134+
135+ if (MemberKind == MemberType.Method)
136+ {
137+ hash.Add(GetTypeHashCode(ReturnType));
138+ if (Parameters is not null)
139+ {
140+ foreach (var param in Parameters)
141+ {
142+ hash.Add(GetTypeHashCode(param));
143+ }
144+ }
145+ }
146+
147+ return hash.ToHashCode();
148+ }
149+
150+ private static int GetTypeHashCode(Type? type)
151+ {
152+ if (type == null) return 0;
153+ return type.IsGenericType ? type.GetGenericTypeDefinition().GetHashCode() : type.GetHashCode();
154+ }
155+
156+ public static MemberKey FromMethodInfo(MethodInfo method)
157+ {
158+ return new MemberKey(
159+ method.DeclaringType,
160+ MemberType.Method,
161+ method.Name,
162+ method.ReturnType.IsGenericParameter ? typeof(object) : method.ReturnType,
163+ method.GetParameters().Select(p => p.ParameterType.IsGenericParameter ? typeof(object) : p.ParameterType).ToArray());
164+ }
165+
166+ public static MemberKey FromPropertyInfo(PropertyInfo property)
167+ {
168+ return new MemberKey(
169+ property.DeclaringType,
170+ MemberType.Property,
171+ property.Name,
172+ null,
173+ null);
174+ }
175+
176+ public static MemberKey FromTypeInfo(Type type)
177+ {
178+ return new MemberKey(
179+ type,
180+ MemberType.Type,
181+ null,
182+ null,
183+ null);
184+ }
185+ }
186+
187+ file enum MemberType
188+ {
189+ Type,
190+ Property,
191+ Method
192+ }
193+
77194 {{ GeneratedCodeAttribute }}
78195 file static class XmlCommentCache
79196 {
80- private static Dictionary<(Type?, string?) , XmlComment>? _cache;
81- public static Dictionary<(Type?, string?) , XmlComment> Cache => _cache ??= GenerateCacheEntries();
197+ private static Dictionary<MemberKey , XmlComment>? _cache;
198+ public static Dictionary<MemberKey , XmlComment> Cache => _cache ??= GenerateCacheEntries();
82199
83- private static Dictionary<(Type?, string?) , XmlComment> GenerateCacheEntries()
200+ private static Dictionary<MemberKey , XmlComment> GenerateCacheEntries()
84201 {
85- var _cache = new Dictionary<(Type?, string?) , XmlComment>();
202+ var _cache = new Dictionary<MemberKey , XmlComment>();
86203{{ commentsFromXmlFile }}
87204{{ commentsFromCompilation }}
88205 return _cache;
89206 }
90207
91- internal static bool TryGetXmlComment(Type? type, string? memberName , [NotNullWhen(true)] out XmlComment? xmlComment)
208+ internal static bool TryGetXmlComment(Type? type, MethodInfo? methodInfo , [NotNullWhen(true)] out XmlComment? xmlComment)
92209 {
93- if (type is not null && type.IsGenericType )
210+ if (methodInfo is null)
94211 {
95- type = type.GetGenericTypeDefinition( );
212+ return Cache.TryGetValue(new MemberKey( type, MemberType.Property, null, null, null), out xmlComment );
96213 }
97214
98- return XmlCommentCache.Cache.TryGetValue((type, memberName), out xmlComment);
215+ return Cache.TryGetValue(MemberKey.FromMethodInfo(methodInfo), out xmlComment);
216+ }
217+
218+ internal static bool TryGetXmlComment(Type? type, string? memberName, [NotNullWhen(true)] out XmlComment? xmlComment)
219+ {
220+ return Cache.TryGetValue(new MemberKey(type, memberName is null ? MemberType.Type : MemberType.Property, memberName, null, null), out xmlComment);
99221 }
100222 }
101223
@@ -112,7 +234,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
112234 {
113235 return Task.CompletedTask;
114236 }
115- if (XmlCommentCache.TryGetXmlComment(methodInfo.DeclaringType, methodInfo.Name , out var methodComment))
237+ if (XmlCommentCache.TryGetXmlComment(methodInfo.DeclaringType, methodInfo, out var methodComment))
116238 {
117239 if (methodComment.Summary is { } summary)
118240 {
@@ -167,7 +289,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
167289 {
168290 response.Value.Description = responseComment.Description;
169291 }
170-
171292 }
172293 }
173294 }
@@ -192,8 +313,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
192313 }
193314 }
194315 }
195- System.Diagnostics.Debugger.Break();
196- if (XmlCommentCache.TryGetXmlComment(context.JsonTypeInfo.Type, null, out var typeComment))
316+ if (XmlCommentCache.TryGetXmlComment(context.JsonTypeInfo.Type, (string?)null, out var typeComment))
197317 {
198318 schema.Description = typeComment.Summary;
199319 if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
@@ -268,9 +388,9 @@ public static IServiceCollection AddOpenApi(this IServiceCollection services, Ac
268388 {
269389 return services.AddOpenApi("v1", options =>
270390 {
271- configureOptions(options);
272391 options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
273392 options.AddOperationTransformer(new XmlCommentOperationTransformer());
393+ configureOptions(options);
274394 });
275395 }
276396 """ ,
@@ -280,9 +400,9 @@ public static IServiceCollection AddOpenApi(this IServiceCollection services, st
280400 // This overload is not intercepted.
281401 return OpenApiServiceCollectionExtensions.AddOpenApi(services, documentName, options =>
282402 {
283- configureOptions(options);
284403 options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
285404 options.AddOperationTransformer(new XmlCommentOperationTransformer());
405+ configureOptions(options);
286406 });
287407 }
288408 """ ,
@@ -307,20 +427,29 @@ internal static string GenerateAddOpenApiInterceptions(ImmutableArray<(AddOpenAp
307427 return writer . ToString ( ) ;
308428 }
309429
310- internal static string EmitCommentsCache ( IEnumerable < ( string , string ? , XmlComment ? ) > comments , CancellationToken cancellationToken )
430+ internal static string EmitCommentsCache ( IEnumerable < ( MemberKey MemberKey , XmlComment ? Comment ) > comments , CancellationToken cancellationToken )
311431 {
312432 var writer = new StringWriter ( ) ;
313433 var codeWriter = new CodeWriter ( writer , baseIndent : 3 ) ;
314- foreach ( var ( type , member , comment ) in comments )
434+ foreach ( var ( memberKey , comment ) in comments )
315435 {
316436 if ( comment is not null )
317437 {
318- var typeKey = $ "(typeof({ type } )";
319- var memberKey = member is not null ? $ "{ SymbolDisplay . FormatLiteral ( member , true ) } " : "null" ;
320- codeWriter . WriteLine ( $ "_cache.Add({ typeKey } , { memberKey } ), { EmitSourceGeneratedXmlComment ( comment ) } );") ;
438+ codeWriter . WriteLine ( $ "_cache.Add(new MemberKey(" +
439+ $ "{ FormatLiteralOrNull ( memberKey . DeclaringType ) } , " +
440+ $ "MemberType.{ memberKey . MemberKind } , " +
441+ $ "{ FormatLiteralOrNull ( memberKey . Name , true ) } , " +
442+ $ "{ FormatLiteralOrNull ( memberKey . ReturnType ) } , " +
443+ $ "[{ ( memberKey . Parameters != null ? string . Join ( ", " , memberKey . Parameters . Select ( p => SymbolDisplay . FormatLiteral ( p , false ) ) ) : "" ) } ]), " +
444+ $ "{ EmitSourceGeneratedXmlComment ( comment ) } );") ;
321445 }
322446 }
323447 return writer . ToString ( ) ;
448+
449+ static string FormatLiteralOrNull ( string ? input , bool quote = false )
450+ {
451+ return input == null ? "null" : SymbolDisplay . FormatLiteral ( input , quote ) ;
452+ }
324453 }
325454
326455 private static string FormatStringForCode ( string ? input )
0 commit comments