Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 67 additions & 111 deletions AspNetCore.sln

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions eng/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="StackExchange.Redis" />
<LatestPackageReference Include="Swashbuckle.AspNetCore" />
<LatestPackageReference Include="System.Reactive.Linq" />
<LatestPackageReference Include="Verify.SourceGenerators" />
<LatestPackageReference Include="Verify.Xunit" />
<LatestPackageReference Include="xunit.abstractions" />
<LatestPackageReference Include="xunit.analyzers" />
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@
<StackExchangeRedisVersion>2.7.27</StackExchangeRedisVersion>
<SystemReactiveLinqVersion>5.0.0</SystemReactiveLinqVersion>
<SwashbuckleAspNetCoreVersion>6.6.2</SwashbuckleAspNetCoreVersion>
<VerifySourceGeneratorsVersion>2.2.0</VerifySourceGeneratorsVersion>
<VerifyXunitVersion>19.14.0</VerifyXunitVersion>
<XunitAbstractionsVersion>2.0.3</XunitAbstractionsVersion>
<XunitAnalyzersVersion>1.15.0</XunitAnalyzersVersion>
Expand Down
2 changes: 2 additions & 0 deletions src/OpenApi/OpenApi.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
"src\\Http\\Routing\\src\\Microsoft.AspNetCore.Routing.csproj",
"src\\OpenApi\\gen\\Microsoft.AspNetCore.OpenApi.SourceGenerators.csproj",
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj",
"src\\OpenApi\\sample\\Sample.csproj",
"src\\OpenApi\\src\\Microsoft.AspNetCore.OpenApi.csproj",
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests\\Microsoft.AspNetCore.OpenApi.SourceGenerators.Tests.csproj",
"src\\OpenApi\\test\\Microsoft.AspNetCore.OpenApi.Tests\\Microsoft.AspNetCore.OpenApi.Tests.csproj",
"src\\OpenApi\\sample\\Sample.csproj",
"src\\OpenApi\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OpenApi.Microbenchmarks.csproj"
Expand Down
20 changes: 20 additions & 0 deletions src/OpenApi/build/Microsoft.AspNetCore.OpenApi.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<PropertyGroup>
<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
<InterceptorsPreviewNamespaces>
$(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsPreviewNamespaces>
</PropertyGroup>
<Target Name="GenerateAdditionalXmlFilesForOpenApi"
AfterTargets="ResolveReferences">
<ItemGroup>
<AdditionalFiles
Include="@(ReferencePath->'%(RootDir)%(Directory)%(Filename).xml')"
Condition="'%(ReferencePath.Extension)' == '.dll' AND
Exists('%(ReferencePath.RootDir)%(ReferencePath.Directory)%(ReferencePath.Filename).xml') AND
('%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference' OR
'%(ReferencePath.NuGetSourceType)' == 'Package')"
KeepMetadata="Identity" />
</ItemGroup>
</Target>
</Project>
18 changes: 18 additions & 0 deletions src/OpenApi/gen/Helpers/AddOpenApiInvocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;

/// <summary>
/// Represents an invocation of the AddOpenApi method.
/// </summary>
/// <param name="Variant">The variant of the AddOpenApi method.</param>
/// <param name="InvocationExpression">The invocation expression.</param>
/// <param name="Location">The location of the invocation that can be intercepted.</param>
internal sealed record AddOpenApiInvocation(
AddOpenApiOverloadVariant Variant,
InvocationExpressionSyntax InvocationExpression,
InterceptableLocation? Location);
27 changes: 27 additions & 0 deletions src/OpenApi/gen/Helpers/AddOpenApiInvocationComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;

internal class AddOpenApiInvocationComparer : IEqualityComparer<AddOpenApiInvocation>
{
public static AddOpenApiInvocationComparer Instance { get; } = new();
public bool Equals(AddOpenApiInvocation? x, AddOpenApiInvocation? y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}

return x.Variant.Equals(y.Variant);
}

public int GetHashCode(AddOpenApiInvocation obj) =>
HashCode.Combine(obj.Variant);
}
13 changes: 13 additions & 0 deletions src/OpenApi/gen/Helpers/AddOpenApiOverloadVariant.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;

internal enum AddOpenApiOverloadVariant
{
AddOpenApi,
AddOpenApiDocumentName,
AddOpenApiDocumentNameConfigureOptions,
AddOpenApiConfigureOptions,
Unknown
}
82 changes: 82 additions & 0 deletions src/OpenApi/gen/Helpers/AssemblyTypeSymbolsVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis;

namespace Microsoft.AspNetCore.OpenApi.SourceGenerators;

/// <summary>
/// Visits the assembly symbols to collect public types, properties, and methods that might
/// contain XML documentation comments.
/// </summary>
internal sealed class AssemblyTypeSymbolsVisitor(IAssemblySymbol assemblySymbol, CancellationToken cancellation) : SymbolVisitor
{
private readonly CancellationToken _cancellationToken = cancellation;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: when do you decide to drop a primary constructor arg into an explicit field on a class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I didn't commit a fix to this. I think this might have been caught in a refactor. I wasn't previously passing in the CancellationToken via the primary constructor.

private readonly HashSet<INamedTypeSymbol> _exportedTypes = new(SymbolEqualityComparer.Default);
private readonly HashSet<IPropertySymbol> _exportedProperties = new(SymbolEqualityComparer.Default);
private readonly HashSet<IMethodSymbol> _exportedMethods = new(SymbolEqualityComparer.Default);

public ImmutableArray<INamedTypeSymbol> GetPublicTypes() => [.. _exportedTypes];
public ImmutableArray<IPropertySymbol> GetPublicProperties() => [.. _exportedProperties];
public ImmutableArray<IMethodSymbol> GetPublicMethods() => [.. _exportedMethods];

public void VisitAssembly() => VisitAssembly(assemblySymbol);

public override void VisitAssembly(IAssemblySymbol symbol)
{
_cancellationToken.ThrowIfCancellationRequested();
symbol.GlobalNamespace.Accept(this);
}

public override void VisitNamespace(INamespaceSymbol symbol)
{
foreach (var namespaceOrType in symbol.GetMembers())
{
_cancellationToken.ThrowIfCancellationRequested();
namespaceOrType.Accept(this);
}
}

public override void VisitNamedType(INamedTypeSymbol type)
{
_cancellationToken.ThrowIfCancellationRequested();

if (!IsDeclaredInAssembly(type) || !_exportedTypes.Add(type))
{
return;
}

var nestedTypes = type.GetTypeMembers();

foreach (var nestedType in nestedTypes)
{
_cancellationToken.ThrowIfCancellationRequested();
nestedType.Accept(this);
}

var properties = type.GetMembers().OfType<IPropertySymbol>();
foreach (var property in properties)
{
_cancellationToken.ThrowIfCancellationRequested();
if (IsDeclaredInAssembly(property) && _exportedProperties.Add(property))
{
property.Type.Accept(this);
}
}
var methods = type.GetMembers().OfType<IMethodSymbol>();
foreach (var method in methods)
{
_cancellationToken.ThrowIfCancellationRequested();
if (IsDeclaredInAssembly(method) && _exportedMethods.Add(method))
{
method.Accept(this);
}
}
}

private bool IsDeclaredInAssembly(ISymbol symbol) =>
SymbolEqualityComparer.Default.Equals(symbol.ContainingAssembly, assemblySymbol);
}
61 changes: 61 additions & 0 deletions src/OpenApi/gen/Helpers/DocumentationCommentXmlNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Microsoft.AspNetCore.OpenApi.SourceGenerators.Xml;

internal static class DocumentationCommentXmlNames
{
public const string CElementName = "c";
public const string CodeElementName = "code";
public const string CompletionListElementName = "completionlist";
public const string DescriptionElementName = "description";
public const string ExampleElementName = "example";
public const string ExceptionElementName = "exception";
public const string IncludeElementName = "include";
public const string InheritdocElementName = "inheritdoc";
public const string ItemElementName = "item";
public const string ListElementName = "list";
public const string ListHeaderElementName = "listheader";
public const string ParaElementName = "para";
public const string ParameterElementName = "param";
public const string ParameterReferenceElementName = "paramref";
public const string PermissionElementName = "permission";
public const string PlaceholderElementName = "placeholder";
public const string PreliminaryElementName = "preliminary";
public const string RemarksElementName = "remarks";
public const string ReturnsElementName = "returns";
public const string SeeElementName = "see";
public const string SeeAlsoElementName = "seealso";
public const string SummaryElementName = "summary";
public const string TermElementName = "term";
public const string ThreadSafetyElementName = "threadsafety";
public const string TypeParameterElementName = "typeparam";
public const string TypeParameterReferenceElementName = "typeparamref";
public const string ValueElementName = "value";
public const string CrefAttributeName = "cref";
public const string HrefAttributeName = "href";
public const string FileAttributeName = "file";
public const string InstanceAttributeName = "instance";
public const string LangwordAttributeName = "langword";
public const string NameAttributeName = "name";
public const string PathAttributeName = "path";
public const string StaticAttributeName = "static";
public const string TypeAttributeName = "type";

public static bool ElementEquals(string name1, string name2, bool fromVb = false)
{
return string.Equals(name1, name2, fromVb ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
}

public static bool AttributeEquals(string name1, string name2)
{
return string.Equals(name1, name2, StringComparison.Ordinal);
}

public static new bool Equals(object left, object right)
{
return object.Equals(left, right);
}
}
Loading
Loading