Skip to content

Commit de379c3

Browse files
authored
Light up System.ClientModel interface decorations on public API surface (#8386)
1 parent fdf8387 commit de379c3

3 files changed

Lines changed: 81 additions & 8 deletions

File tree

src/dotnet/APIView/APIView/Languages/CompilationFactory.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ public static class CompilationFactory
1313
{
1414
private static HashSet<string> AllowedAssemblies = new HashSet<string>(new[]
1515
{
16-
"Microsoft.Bcl.AsyncInterfaces"
16+
"Microsoft.Bcl.AsyncInterfaces",
17+
"System.ClientModel"
18+
1719
}, StringComparer.InvariantCultureIgnoreCase);
1820

1921
public static IAssemblySymbol GetCompilation(string file)
@@ -24,7 +26,7 @@ public static IAssemblySymbol GetCompilation(string file)
2426
}
2527
}
2628

27-
public static IAssemblySymbol GetCompilation(Stream stream, Stream documentationStream)
29+
public static IAssemblySymbol GetCompilation(Stream stream, Stream documentationStream, IEnumerable<string> dependencyPaths = null)
2830
{
2931
PortableExecutableReference reference;
3032

@@ -59,6 +61,17 @@ public static IAssemblySymbol GetCompilation(Stream stream, Stream documentation
5961
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(tpl));
6062
}
6163
}
64+
if (dependencyPaths != null)
65+
{
66+
foreach (var dependencyFile in dependencyPaths)
67+
{
68+
if (!File.Exists(dependencyFile) || !AllowedAssemblies.Contains(Path.GetFileNameWithoutExtension(dependencyFile)))
69+
{
70+
continue;
71+
}
72+
compilation = compilation.AddReferences(MetadataReference.CreateFromFile(dependencyFile));
73+
}
74+
}
6275

6376
return (IAssemblySymbol)compilation.GetAssemblyOrModuleSymbol(reference);
6477
}

src/dotnet/APIView/APIViewWeb/APIViewWeb.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-19367-01" PrivateAssets="All" />
5858
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
5959
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
60+
<PackageReference Include="NuGet.Protocol" Version="6.10.0" />
6061
<PackageReference Include="Octokit" Version="9.1.0" />
6162
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
6263
</ItemGroup>

src/dotnet/APIView/APIViewWeb/Languages/CSharpLanguageService.cs

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
using System.IO.Compression;
88
using System.Linq;
99
using System.Text.RegularExpressions;
10+
using System.Threading;
1011
using System.Threading.Tasks;
1112
using System.Xml.Linq;
1213
using ApiView;
14+
using NuGet.Common;
15+
using NuGet.Packaging;
16+
using NuGet.Protocol;
17+
using NuGet.Protocol.Core.Types;
18+
using NuGet.Versioning;
1319

1420
namespace APIViewWeb
1521
{
@@ -44,9 +50,10 @@ public override bool CanUpdate(string versionString)
4450
return versionString != CodeFileBuilder.CurrentVersion;
4551
}
4652

47-
public override Task<CodeFile> GetCodeFileAsync(string originalName, Stream stream, bool runAnalysis)
53+
public override async Task<CodeFile> GetCodeFileAsync(string originalName, Stream stream, bool runAnalysis)
4854
{
4955
ZipArchive archive = null;
56+
string dependencyFilesTempDir = null;
5057
try
5158
{
5259
Stream dllStream = stream;
@@ -62,7 +69,7 @@ public override Task<CodeFile> GetCodeFileAsync(string originalName, Stream stre
6269
var dllEntries = archive.Entries.Where(entry => IsDll(entry.Name)).ToArray();
6370
if (dllEntries.Length == 0)
6471
{
65-
return Task.FromResult(GetDummyReviewCodeFile(originalName, dependencies));
72+
return GetDummyReviewCodeFile(originalName, dependencies);
6673
}
6774

6875
var dllEntry = dllEntries.First();
@@ -99,17 +106,24 @@ public override Task<CodeFile> GetCodeFileAsync(string originalName, Stream stre
99106
}
100107
}
101108

102-
var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream);
109+
dependencyFilesTempDir = await ExtractNugetDependencies(dependencies).ConfigureAwait(false);
110+
var dependencyFilePaths = Directory.EnumerateFiles(dependencyFilesTempDir, "*.dll", SearchOption.AllDirectories);
111+
112+
var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream, dependencyFilePaths);
103113
if (assemblySymbol == null)
104114
{
105-
return Task.FromResult(GetDummyReviewCodeFile(originalName, dependencies));
115+
return GetDummyReviewCodeFile(originalName, dependencies);
106116
}
107117

108-
return Task.FromResult(new CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies));
118+
return new CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies);
109119
}
110120
finally
111121
{
112122
archive?.Dispose();
123+
if (dependencyFilesTempDir != null && Directory.Exists(dependencyFilesTempDir))
124+
{
125+
Directory.Delete(dependencyFilesTempDir, true);
126+
}
113127
}
114128
}
115129

@@ -132,13 +146,58 @@ private CodeFile GetDummyReviewCodeFile(string originalName, List<DependencyInfo
132146
CodeFileBuilder.BuildDependencies(builder, dependencies);
133147

134148
return new CodeFile()
135-
{
149+
{
136150
Name = reviewName,
137151
Language = "C#",
138152
VersionString = CodeFileBuilder.CurrentVersion,
139153
PackageName = packageName,
140154
Tokens = builder.Tokens.ToArray()
141155
};
142156
}
157+
158+
/// <summary>
159+
/// Resolves the NuGet package dependencies and extracts them to a temporary folder. It is the responsibility of teh caller to clean up the folder.
160+
/// </summary>
161+
/// <param name="dependencyInfos">The dependency infos</param>
162+
/// <returns>A temporary path where the dependency files were extracted.</returns>
163+
private async Task<string> ExtractNugetDependencies(List<DependencyInfo> dependencyInfos)
164+
{
165+
string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
166+
SourceCacheContext cache = new SourceCacheContext();
167+
SourceRepository repository = NuGet.Protocol.Core.Types.Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
168+
try
169+
{
170+
FindPackageByIdResource resource = await repository.GetResourceAsync<FindPackageByIdResource>().ConfigureAwait(false);
171+
foreach (var dep in dependencyInfos)
172+
{
173+
using (MemoryStream packageStream = new MemoryStream())
174+
{
175+
if (await resource.CopyNupkgToStreamAsync(
176+
dep.Name,
177+
new NuGetVersion(dep.Version),
178+
packageStream,
179+
cache,
180+
NullLogger.Instance,
181+
CancellationToken.None))
182+
{
183+
using PackageArchiveReader reader = new PackageArchiveReader(packageStream);
184+
NuspecReader nuspec = reader.NuspecReader;
185+
var file = reader.GetFiles().FirstOrDefault(f => f.EndsWith(dep.Name + ".dll"));
186+
if (file != null)
187+
{
188+
var fileInfo = new FileInfo(file);
189+
var path = Path.Combine(tempFolder, dep.Name, fileInfo.Name);
190+
var tmp = reader.ExtractFile(file, path, NullLogger.Instance);
191+
}
192+
}
193+
}
194+
}
195+
}
196+
finally
197+
{
198+
cache.Dispose();
199+
}
200+
return tempFolder;
201+
}
143202
}
144203
}

0 commit comments

Comments
 (0)