8
8
using Microsoft . CodeAnalysis . CSharp ;
9
9
using Microsoft . AspNetCore . OpenApi . SourceGenerators . Xml ;
10
10
using System . Threading ;
11
+ using System . Linq ;
11
12
12
13
namespace Microsoft . AspNetCore . OpenApi . SourceGenerators ;
13
14
@@ -74,28 +75,149 @@ file record XmlComment(
74
75
{{ GeneratedCodeAttribute }}
75
76
file record XmlResponseComment(string Code, string? Description, string? Example);
76
77
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
+
77
194
{{ GeneratedCodeAttribute }}
78
195
file static class XmlCommentCache
79
196
{
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();
82
199
83
- private static Dictionary<(Type?, string?) , XmlComment> GenerateCacheEntries()
200
+ private static Dictionary<MemberKey , XmlComment> GenerateCacheEntries()
84
201
{
85
- var _cache = new Dictionary<(Type?, string?) , XmlComment>();
202
+ var _cache = new Dictionary<MemberKey , XmlComment>();
86
203
{{ commentsFromXmlFile }}
87
204
{{ commentsFromCompilation }}
88
205
return _cache;
89
206
}
90
207
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)
92
209
{
93
- if (type is not null && type.IsGenericType )
210
+ if (methodInfo is null)
94
211
{
95
- type = type.GetGenericTypeDefinition( );
212
+ return Cache.TryGetValue(new MemberKey( type, MemberType.Property, null, null, null), out xmlComment );
96
213
}
97
214
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);
99
221
}
100
222
}
101
223
@@ -112,7 +234,7 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
112
234
{
113
235
return Task.CompletedTask;
114
236
}
115
- if (XmlCommentCache.TryGetXmlComment(methodInfo.DeclaringType, methodInfo.Name , out var methodComment))
237
+ if (XmlCommentCache.TryGetXmlComment(methodInfo.DeclaringType, methodInfo, out var methodComment))
116
238
{
117
239
if (methodComment.Summary is { } summary)
118
240
{
@@ -167,7 +289,6 @@ public Task TransformAsync(OpenApiOperation operation, OpenApiOperationTransform
167
289
{
168
290
response.Value.Description = responseComment.Description;
169
291
}
170
-
171
292
}
172
293
}
173
294
}
@@ -192,8 +313,7 @@ public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext
192
313
}
193
314
}
194
315
}
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))
197
317
{
198
318
schema.Description = typeComment.Summary;
199
319
if (typeComment.Examples?.FirstOrDefault() is { } jsonString)
@@ -268,9 +388,9 @@ public static IServiceCollection AddOpenApi(this IServiceCollection services, Ac
268
388
{
269
389
return services.AddOpenApi("v1", options =>
270
390
{
271
- configureOptions(options);
272
391
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
273
392
options.AddOperationTransformer(new XmlCommentOperationTransformer());
393
+ configureOptions(options);
274
394
});
275
395
}
276
396
""" ,
@@ -280,9 +400,9 @@ public static IServiceCollection AddOpenApi(this IServiceCollection services, st
280
400
// This overload is not intercepted.
281
401
return OpenApiServiceCollectionExtensions.AddOpenApi(services, documentName, options =>
282
402
{
283
- configureOptions(options);
284
403
options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
285
404
options.AddOperationTransformer(new XmlCommentOperationTransformer());
405
+ configureOptions(options);
286
406
});
287
407
}
288
408
""" ,
@@ -307,20 +427,29 @@ internal static string GenerateAddOpenApiInterceptions(ImmutableArray<(AddOpenAp
307
427
return writer . ToString ( ) ;
308
428
}
309
429
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 )
311
431
{
312
432
var writer = new StringWriter ( ) ;
313
433
var codeWriter = new CodeWriter ( writer , baseIndent : 3 ) ;
314
- foreach ( var ( type , member , comment ) in comments )
434
+ foreach ( var ( memberKey , comment ) in comments )
315
435
{
316
436
if ( comment is not null )
317
437
{
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 ) } );") ;
321
445
}
322
446
}
323
447
return writer . ToString ( ) ;
448
+
449
+ static string FormatLiteralOrNull ( string ? input , bool quote = false )
450
+ {
451
+ return input == null ? "null" : SymbolDisplay . FormatLiteral ( input , quote ) ;
452
+ }
324
453
}
325
454
326
455
private static string FormatStringForCode ( string ? input )
0 commit comments