-
Notifications
You must be signed in to change notification settings - Fork 10.3k
/
Copy pathCompletenessTests.cs
433 lines (395 loc) · 14 KB
/
CompletenessTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.OpenApi.Models;
namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests;
// Test scenarios derived from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/examples
[UsesVerify]
public class CompletenessTests
{
[Fact]
public async Task SupportsAllXmlTagsOnSchemas()
{
var source = """
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder();
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapPost("/example-class", (ExampleClass example) => { });
app.MapPost("/person", (Person person) => { });
app.MapPost("/derived-class", (DerivedClass child) => { });
app.MapPost("/main-class", (MainClass main) => { });
app.MapPost("/test-interface", (ITestInterface test) => { });
app.MapPost("/implementing-class", (ImplementingClass impl) => { });
app.MapPost("/inherit-only-returns", (InheritOnlyReturns returns) => { });
app.MapPost("/inherit-all-but-remarks", (InheritAllButRemarks remarks) => { });
app.MapPost("/generic-class", (GenericClass<string> generic) => { });
app.MapPost("/params-and-param-refs", (ParamsAndParamRefs refs) => { });
app.Run();
/// <summary>
/// Every class and member should have a one sentence
/// summary describing its purpose.
/// </summary>
/// <remarks>
/// You can expand on that one sentence summary to
/// provide more information for readers. In this case,
/// the <c>ExampleClass</c> provides different C#
/// elements to show how you would add documentation
///comments for most elements in a typical class.
/// <para>
/// The remarks can add multiple paragraphs, so you can
/// write detailed information for developers that use
/// your work. You should add everything needed for
/// readers to be successful. This class contains
/// examples for the following:
/// </para>
/// <list type="table">
/// <item>
/// <term>Summary</term>
/// <description>
/// This should provide a one sentence summary of the class or member.
/// </description>
/// </item>
/// <item>
/// <term>Remarks</term>
/// <description>
/// This is typically a more detailed description of the class or member
/// </description>
/// </item>
/// <item>
/// <term>para</term>
/// <description>
/// The para tag separates a section into multiple paragraphs
/// </description>
/// </item>
/// <item>
/// <term>list</term>
/// <description>
/// Provides a list of terms or elements
/// </description>
/// </item>
/// <item>
/// <term>returns, param</term>
/// <description>
/// Used to describe parameters and return values
/// </description>
/// </item>
/// <item>
/// <term>value</term>
/// <description>Used to describe properties</description>
/// </item>
/// <item>
/// <term>exception</term>
/// <description>
/// Used to describe exceptions that may be thrown
/// </description>
/// </item>
/// <item>
/// <term>c, cref, see, seealso</term>
/// <description>
/// These provide code style and links to other
/// documentation elements
/// </description>
/// </item>
/// <item>
/// <term>example, code</term>
/// <description>
/// These are used for code examples
/// </description>
/// </item>
/// </list>
/// <para>
/// The list above uses the "table" style. You could
/// also use the "bullet" or "number" style. Neither
/// would typically use the "term" element.
/// <br/>
/// Note: paragraphs are double spaced. Use the *br*
/// tag for single spaced lines.
/// </para>
/// </remarks>
public class ExampleClass
{
/// <value>
/// The <c>Label</c> property represents a label
/// for this instance.
/// </value>
/// <remarks>
/// The <see cref="Label"/> is a <see langword="string"/>
/// that you use for a label.
/// <para>
/// Note that there isn't a way to provide a "cref" to
/// each accessor, only to the property itself.
/// </para>
/// </remarks>
public string? Label
{
get;
set;
}
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <param name="left">
/// The left operand of the addition.
/// </param>
/// <param name="right">
/// The right operand of the addition.
/// </param>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">
/// Thrown when one parameter is
/// <see cref="Int32.MaxValue">MaxValue</see> and the other is
/// greater than 0.
/// Note that here you can also use
/// <see href="https://learn.microsoft.com/dotnet/api/system.int32.maxvalue"/>
/// to point a web page instead.
/// </exception>
/// <see cref="ExampleClass"/> for a list of all
/// the tags in these examples.
/// <seealso cref="ExampleClass.Label"/>
public static int Add(int left, int right)
{
if ((left == int.MaxValue && right > 0) || (right == int.MaxValue && left > 0))
throw new System.OverflowException();
return left + right;
}
/// <summary>
/// This method is an example of a method that
/// returns an awaitable item.
/// </summary>
public static Task<int> AddAsync(int left, int right)
{
return Task.FromResult(Add(left, right));
}
/// <summary>
/// This method is an example of a method that
/// returns a Task which should map to a void return type.
/// </summary>
public static Task DoNothingAsync()
{
return Task.CompletedTask;
}
/// <summary>
/// This method is an example of a method that consumes
/// an params array.
/// </summary>
public static int AddNumbers(params int[] numbers)
{
var sum = 0;
foreach (var number in numbers)
{
sum += number;
}
return sum;
}
}
/// <summary>
/// This is an example of a positional record.
/// </summary>
/// <remarks>
/// There isn't a way to add XML comments for properties
/// created for positional records, yet. The language
/// design team is still considering what tags should
/// be supported, and where. Currently, you can use
/// the "param" tag to describe the parameters to the
/// primary constructor.
/// </remarks>
/// <param name="FirstName">
/// This tag will apply to the primary constructor parameter.
/// </param>
/// <param name="LastName">
/// This tag will apply to the primary constructor parameter.
/// </param>
public record Person(string FirstName, string LastName);
/// <summary>
/// A summary about this class.
/// </summary>
/// <remarks>
/// These remarks would explain more about this class.
/// In this example, these comments also explain the
/// general information about the derived class.
/// </remarks>
public class MainClass
{
}
///<inheritdoc/>
public class DerivedClass : MainClass
{
}
/// <summary>
/// This interface would describe all the methods in
/// its contract.
/// </summary>
/// <remarks>
/// While elided for brevity, each method or property
/// in this interface would contain docs that you want
/// to duplicate in each implementing class.
/// </remarks>
public interface ITestInterface
{
/// <summary>
/// This method is part of the test interface.
/// </summary>
/// <remarks>
/// This content would be inherited by classes
/// that implement this interface when the
/// implementing class uses "inheritdoc"
/// </remarks>
/// <returns>The value of <paramref name="arg" /> </returns>
/// <param name="arg">The argument to the method</param>
int Method(int arg);
}
///<inheritdoc cref="ITestInterface"/>
public class ImplementingClass : ITestInterface
{
// doc comments are inherited here.
public int Method(int arg) => arg;
}
/// <summary>
/// This class shows hows you can "inherit" the doc
/// comments from one method in another method.
/// </summary>
/// <remarks>
/// You can inherit all comments, or only a specific tag,
/// represented by an xpath expression.
/// </remarks>
public class InheritOnlyReturns
{
/// <summary>
/// In this example, this summary is only visible for this method.
/// </summary>
/// <returns>A boolean</returns>
public static bool MyParentMethod(bool x) { return x; }
/// <inheritdoc cref="MyParentMethod" path="/returns"/>
public static bool MyChildMethod() { return false; }
}
/// <summary>
/// This class shows an example of sharing comments across methods.
/// </summary>
public class InheritAllButRemarks
{
/// <summary>
/// In this example, this summary is visible on all the methods.
/// </summary>
/// <remarks>
/// The remarks can be inherited by other methods
/// using the xpath expression.
/// </remarks>
/// <returns>A boolean</returns>
public static bool MyParentMethod(bool x) { return x; }
/// <inheritdoc cref="MyParentMethod" path="//*[not(self::remarks)]"/>
public static bool MyChildMethod() { return false; }
}
/// <summary>
/// This is a generic class.
/// </summary>
/// <remarks>
/// This example shows how to specify the <see cref="GenericClass{T}"/>
/// type as a cref attribute.
/// In generic classes and methods, you'll often want to reference the
/// generic type, or the type parameter.
/// </remarks>
public class GenericClass<T>
{
// Fields and members.
}
/// <summary>
/// This shows examples of typeparamref and typeparam tags
/// </summary>
public class ParamsAndParamRefs
{
/// <summary>
/// The GetGenericValue method.
/// </summary>
/// <remarks>
/// This sample shows how to specify the <see cref="GetGenericValue"/>
/// method as a cref attribute.
/// The parameter and return value are both of an arbitrary type,
/// <typeparamref name="T"/>
/// </remarks>
public static T GetGenericValue<T>(T para)
{
return para;
}
}
/// <summary>
/// A class that implements the <see cref="IDisposable"/> interface.
/// </summary>
public class DisposableType : IDisposable
{
/// <summary>
/// Finalizes an instance of the <see cref="DisposableType"/> class.
/// </summary>
~DisposableType()
{
Dispose(false);
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
// No-op
}
}
""";
var generator = new XmlCommentGenerator();
await SnapshotTestHelper.Verify(source, generator, out var compilation);
await SnapshotTestHelper.VerifyOpenApi(compilation, document =>
{
var path = document.Paths["/example-class"].Operations[OperationType.Post];
var exampleClass = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("Every class and member should have a one sentence\nsummary describing its purpose.", exampleClass.Description, ignoreLineEndingDifferences: true);
Assert.Equal("The `Label` property represents a label\nfor this instance.", exampleClass.Properties["label"].Description, ignoreLineEndingDifferences: true);
path = document.Paths["/person"].Operations[OperationType.Post];
var person = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This is an example of a positional record.", person.Description);
Assert.Equal("This tag will apply to the primary constructor parameter.", person.Properties["firstName"].Description);
Assert.Equal("This tag will apply to the primary constructor parameter.", person.Properties["lastName"].Description);
path = document.Paths["/derived-class"].Operations[OperationType.Post];
var derivedClass = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("A summary about this class.", derivedClass.Description);
path = document.Paths["/main-class"].Operations[OperationType.Post];
var mainClass = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("A summary about this class.", mainClass.Description);
path = document.Paths["/implementing-class"].Operations[OperationType.Post];
var implementingClass = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This interface would describe all the methods in\nits contract.", implementingClass.Description, ignoreLineEndingDifferences: true);
path = document.Paths["/inherit-only-returns"].Operations[OperationType.Post];
var inheritOnlyReturns = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This class shows hows you can \"inherit\" the doc\ncomments from one method in another method.", inheritOnlyReturns.Description, ignoreLineEndingDifferences: true);
path = document.Paths["/inherit-all-but-remarks"].Operations[OperationType.Post];
var inheritAllButRemarks = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This class shows an example of sharing comments across methods.", inheritAllButRemarks.Description);
path = document.Paths["/generic-class"].Operations[OperationType.Post];
var genericClass = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This is a generic class.", genericClass.Description);
path = document.Paths["/params-and-param-refs"].Operations[OperationType.Post];
var paramsAndParamRefs = path.RequestBody.Content["application/json"].Schema;
Assert.Equal("This shows examples of typeparamref and typeparam tags", paramsAndParamRefs.Description);
});
}
}