Skip to content

Commit 7a34525

Browse files
Add Tests
1 parent 150dcab commit 7a34525

4 files changed

Lines changed: 223 additions & 160 deletions

File tree

tools/apiview/parsers/csharp-api-parser/CSharpAPIParser/Program.cs

Lines changed: 170 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -10,210 +10,220 @@
1010
using NuGet.Protocol.Core.Types;
1111
using NuGet.Versioning;
1212

13-
var inputOption = new Option<FileInfo>("--packageFilePath", "C# Package (.nupkg) file").ExistingOnly();
14-
inputOption.IsRequired = true;
1513

16-
var outputOption1 = new Option<DirectoryInfo>("--outputDirectoryPath", "Directory for the output Token File").ExistingOnly();
17-
var outputOption2 = new Option<string>("--outputFileName", "Output File Name");
18-
var runAnalysis = new Argument<bool>("runAnalysis", "Run Analysis on the package");
19-
runAnalysis.SetDefaultValue(true);
20-
21-
var rootCommand = new RootCommand("Parse C# Package (.nupkg) to APIView Tokens")
22-
{
23-
inputOption,
24-
outputOption1,
25-
outputOption2,
26-
runAnalysis
27-
};
28-
29-
rootCommand.SetHandler(async (FileInfo packageFilePath, DirectoryInfo outputDirectory, string outputFileName, bool runAnalysis) =>
14+
public static class Program
3015
{
31-
try
16+
public static int Main(string[] args)
3217
{
33-
using (var stream = packageFilePath.OpenRead())
18+
var inputOption = new Option<FileInfo>("--packageFilePath", "C# Package (.nupkg) file").ExistingOnly();
19+
inputOption.IsRequired = true;
20+
21+
var outputOption1 = new Option<DirectoryInfo>("--outputDirectoryPath", "Directory for the output Token File").ExistingOnly();
22+
var outputOption2 = new Option<string>("--outputFileName", "Output File Name");
23+
var runAnalysis = new Argument<bool>("runAnalysis", "Run Analysis on the package");
24+
runAnalysis.SetDefaultValue(true);
25+
26+
var rootCommand = new RootCommand("Parse C# Package (.nupkg) to APIView Tokens")
3427
{
35-
await HandlePackageFileParsing(stream, packageFilePath, outputDirectory, outputFileName, runAnalysis);
36-
}
37-
}
38-
catch (Exception ex)
39-
{
40-
Console.Error.WriteLine($"Error Reading PackageFile : {ex.Message}");
41-
}
42-
}, inputOption, outputOption1,outputOption2, runAnalysis);
28+
inputOption,
29+
outputOption1,
30+
outputOption2,
31+
runAnalysis
32+
};
4333

44-
return rootCommand.InvokeAsync(args).Result;
34+
rootCommand.SetHandler(async (FileInfo packageFilePath, DirectoryInfo outputDirectory, string outputFileName, bool runAnalysis) =>
35+
{
36+
try
37+
{
38+
using (var stream = packageFilePath.OpenRead())
39+
{
40+
await HandlePackageFileParsing(stream, packageFilePath, outputDirectory, outputFileName, runAnalysis);
41+
}
42+
}
43+
catch (Exception ex)
44+
{
45+
Console.Error.WriteLine($"Error Reading PackageFile : {ex.Message}");
46+
}
47+
}, inputOption, outputOption1, outputOption2, runAnalysis);
4548

49+
return rootCommand.InvokeAsync(args).Result;
50+
}
4651

47-
static async Task HandlePackageFileParsing(Stream stream, FileInfo packageFilePath, DirectoryInfo OutputDirectory, string outputFileName, bool runAnalysis)
48-
{
49-
ZipArchive? zipArchive = null;
50-
Stream? dllStream = stream;
51-
Stream? docStream = null;
52-
List<DependencyInfo>? dependencies = new List<DependencyInfo>();
53-
string? dependencyFilesTempDir = null;
5452

55-
try
53+
static async Task HandlePackageFileParsing(Stream stream, FileInfo packageFilePath, DirectoryInfo OutputDirectory, string outputFileName, bool runAnalysis)
5654
{
57-
if (IsNuget(packageFilePath.FullName))
58-
{
59-
zipArchive = new ZipArchive(stream, ZipArchiveMode.Read);
60-
var nuspecEntry = zipArchive.Entries.Single(entry => IsNuspec(entry.Name));
61-
var dllEntries = zipArchive.Entries.Where(entry => IsDll(entry.Name)).ToArray();
55+
ZipArchive? zipArchive = null;
56+
Stream? dllStream = stream;
57+
Stream? docStream = null;
58+
List<DependencyInfo>? dependencies = new List<DependencyInfo>();
59+
string? dependencyFilesTempDir = null;
6260

63-
if (dllEntries.Length == 0)
61+
try
62+
{
63+
if (IsNuget(packageFilePath.FullName))
6464
{
65-
Console.Error.WriteLine($"PackageFile {packageFilePath.FullName} contains no dlls.");
66-
return;
67-
}
65+
zipArchive = new ZipArchive(stream, ZipArchiveMode.Read);
66+
var nuspecEntry = zipArchive.Entries.Single(entry => IsNuspec(entry.Name));
67+
var dllEntries = zipArchive.Entries.Where(entry => IsDll(entry.Name)).ToArray();
6868

69-
var dllEntry = dllEntries.First();
70-
if (dllEntries.Length > 1)
71-
{
72-
// If there are multiple dlls in the nupkg (e.g. Cosmos), try to find the first that matches the nuspec name, but
73-
// fallback to just using the first one.
74-
dllEntry = dllEntries.FirstOrDefault(
75-
dll => Path.GetFileNameWithoutExtension(nuspecEntry.Name)
76-
.Equals(Path.GetFileNameWithoutExtension(dll.Name), StringComparison.OrdinalIgnoreCase)) ?? dllEntry;
69+
if (dllEntries.Length == 0)
70+
{
71+
Console.Error.WriteLine($"PackageFile {packageFilePath.FullName} contains no dlls.");
72+
return;
73+
}
74+
75+
var dllEntry = dllEntries.First();
76+
if (dllEntries.Length > 1)
77+
{
78+
// If there are multiple dlls in the nupkg (e.g. Cosmos), try to find the first that matches the nuspec name, but
79+
// fallback to just using the first one.
80+
dllEntry = dllEntries.FirstOrDefault(
81+
dll => Path.GetFileNameWithoutExtension(nuspecEntry.Name)
82+
.Equals(Path.GetFileNameWithoutExtension(dll.Name), StringComparison.OrdinalIgnoreCase)) ?? dllEntry;
83+
}
84+
85+
dllStream = dllEntry.Open();
86+
var docEntry = zipArchive.GetEntry(Path.ChangeExtension(dllEntry.FullName, ".xml"));
87+
if (docEntry != null)
88+
{
89+
docStream = docEntry.Open();
90+
}
91+
using var nuspecStream = nuspecEntry.Open();
92+
var document = XDocument.Load(nuspecStream);
93+
var dependencyElements = document.Descendants().Where(e => e.Name.LocalName == "dependency");
94+
dependencies.AddRange(
95+
dependencyElements.Select(dependency => new DependencyInfo(
96+
dependency.Attribute("id")?.Value,
97+
SelectSpecificVersion(dependency.Attribute("version")?.Value))));
98+
// filter duplicates and sort
99+
if (dependencies.Any())
100+
{
101+
dependencies = dependencies
102+
.GroupBy(d => d.Name)
103+
.Select(d => d.First())
104+
.OrderBy(d => d.Name).ToList();
105+
}
77106
}
78107

79-
dllStream = dllEntry.Open();
80-
var docEntry = zipArchive.GetEntry(Path.ChangeExtension(dllEntry.FullName, ".xml"));
81-
if (docEntry != null)
108+
IEnumerable<string> dependencyFilePaths = new List<string>();
109+
if (dependencies != null && dependencies.Any())
82110
{
83-
docStream = docEntry.Open();
111+
dependencyFilesTempDir = await ExtractNugetDependencies(dependencies).ConfigureAwait(false);
112+
if (Directory.Exists(dependencyFilesTempDir))
113+
{
114+
dependencyFilePaths = Directory.EnumerateFiles(dependencyFilesTempDir, "*.dll", SearchOption.AllDirectories);
115+
}
84116
}
85-
using var nuspecStream = nuspecEntry.Open();
86-
var document = XDocument.Load(nuspecStream);
87-
var dependencyElements = document.Descendants().Where(e => e.Name.LocalName == "dependency");
88-
dependencies.AddRange(
89-
dependencyElements.Select(dependency => new DependencyInfo(
90-
dependency.Attribute("id")?.Value,
91-
SelectSpecificVersion(dependency.Attribute("version")?.Value))));
92-
// filter duplicates and sort
93-
if (dependencies.Any())
117+
var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream, dependencyFilePaths);
118+
119+
if (assemblySymbol == null)
94120
{
95-
dependencies = dependencies
96-
.GroupBy(d => d.Name)
97-
.Select(d => d.First())
98-
.OrderBy(d => d.Name).ToList();
121+
Console.Error.WriteLine($"PackageFile {packageFilePath.FullName} contains no Assembly Symbol.");
122+
return;
99123
}
100-
}
101124

102-
IEnumerable<string> dependencyFilePaths = new List<string>();
103-
if (dependencies != null && dependencies.Any())
104-
{
105-
dependencyFilesTempDir = await ExtractNugetDependencies(dependencies).ConfigureAwait(false);
106-
dependencyFilePaths = Directory.EnumerateFiles(dependencyFilesTempDir, "*.dll", SearchOption.AllDirectories);
107-
}
108-
var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream, dependencyFilePaths);
125+
var parsedFileName = string.IsNullOrEmpty(outputFileName) ? assemblySymbol.Name : outputFileName;
126+
var treeTokenCodeFile = new CSharpAPIParser.TreeToken.CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies);
127+
var gzipJsonTokenFilePath = Path.Combine(OutputDirectory.FullName, $"{parsedFileName}.json.tgz");
109128

110-
if (assemblySymbol == null)
111-
{
112-
Console.Error.WriteLine($"PackageFile {packageFilePath.FullName} contains no Assembly Symbol.");
113-
return;
114-
}
115129

116-
var parsedFileName = string.IsNullOrEmpty(outputFileName) ? assemblySymbol.Name : outputFileName;
117-
var treeTokenCodeFile = new CSharpAPIParser.TreeToken.CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies);
118-
var gzipJsonTokenFilePath = Path.Combine(OutputDirectory.FullName, $"{parsedFileName}.json.tgz");
130+
var options = new JsonSerializerOptions()
131+
{
132+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
133+
};
119134

135+
{
136+
using FileStream gzipFileStream = new FileStream(gzipJsonTokenFilePath, FileMode.Create, FileAccess.Write);
137+
using GZipStream gZipStream = new GZipStream(gzipFileStream, CompressionLevel.Optimal);
138+
JsonSerializer.Serialize(new Utf8JsonWriter(gZipStream, new JsonWriterOptions { Indented = false }), treeTokenCodeFile, options);
139+
}
120140

121-
var options = new JsonSerializerOptions()
141+
Console.WriteLine($"TokenCodeFile File {gzipJsonTokenFilePath} Generated Successfully.");
142+
Console.WriteLine();
143+
}
144+
catch (Exception ex)
122145
{
123-
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
124-
};
125-
146+
Console.Error.WriteLine($"Error Parsing PackageFile : {ex.Message}");
147+
}
148+
finally
126149
{
127-
using FileStream gzipFileStream = new FileStream(gzipJsonTokenFilePath, FileMode.Create, FileAccess.Write);
128-
using GZipStream gZipStream = new GZipStream(gzipFileStream, CompressionLevel.Optimal);
129-
JsonSerializer.Serialize(new Utf8JsonWriter(gZipStream, new JsonWriterOptions { Indented = false }), treeTokenCodeFile, options);
150+
zipArchive?.Dispose();
151+
if (dependencyFilesTempDir != null && Directory.Exists(dependencyFilesTempDir))
152+
{
153+
Directory.Delete(dependencyFilesTempDir, true);
154+
}
130155
}
131-
132-
Console.WriteLine($"TokenCodeFile File {gzipJsonTokenFilePath} Generated Successfully.");
133-
Console.WriteLine();
134156
}
135-
catch (Exception ex)
157+
158+
static bool IsNuget(string name)
136159
{
137-
Console.Error.WriteLine($"Error Parsing PackageFile : {ex.Message}");
160+
return name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase);
138161
}
139-
finally
162+
163+
static bool IsNuspec(string name)
140164
{
141-
zipArchive?.Dispose();
142-
if (dependencyFilesTempDir != null && Directory.Exists(dependencyFilesTempDir))
143-
{
144-
Directory.Delete(dependencyFilesTempDir, true);
145-
}
165+
return name.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase);
146166
}
147-
}
148167

149-
static bool IsNuget(string name)
150-
{
151-
return name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase);
152-
}
153-
154-
static bool IsNuspec(string name)
155-
{
156-
return name.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase);
157-
}
158-
159-
static bool IsDll(string name)
160-
{
161-
return name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase);
162-
}
168+
static bool IsDll(string name)
169+
{
170+
return name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase);
171+
}
163172

164-
/// <summary>
165-
/// Resolves the NuGet package dependencies and extracts them to a temporary folder. It is the responsibility of teh caller to clean up the folder.
166-
/// </summary>
167-
/// <param name="dependencyInfos">The dependency infos</param>
168-
/// <returns>A temporary path where the dependency files were extracted.</returns>
169-
static async Task<string> ExtractNugetDependencies(List<DependencyInfo> dependencyInfos)
170-
{
171-
string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
172-
SourceCacheContext cache = new SourceCacheContext();
173-
SourceRepository repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
174-
try
173+
/// <summary>
174+
/// Resolves the NuGet package dependencies and extracts them to a temporary folder. It is the responsibility of teh caller to clean up the folder.
175+
/// </summary>
176+
/// <param name="dependencyInfos">The dependency infos</param>
177+
/// <returns>A temporary path where the dependency files were extracted.</returns>
178+
public static async Task<string> ExtractNugetDependencies(List<DependencyInfo> dependencyInfos)
175179
{
176-
FindPackageByIdResource resource = await repository.GetResourceAsync<FindPackageByIdResource>().ConfigureAwait(false);
177-
foreach (var dep in dependencyInfos)
180+
string tempFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
181+
SourceCacheContext cache = new SourceCacheContext();
182+
SourceRepository repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");
183+
try
178184
{
179-
using (MemoryStream packageStream = new MemoryStream())
185+
FindPackageByIdResource resource = await repository.GetResourceAsync<FindPackageByIdResource>().ConfigureAwait(false);
186+
foreach (var dep in dependencyInfos)
180187
{
181-
if (await resource.CopyNupkgToStreamAsync(
182-
dep.Name,
183-
new NuGetVersion(dep.Version),
184-
packageStream,
185-
cache,
186-
NullLogger.Instance,
187-
CancellationToken.None))
188+
using (MemoryStream packageStream = new MemoryStream())
188189
{
189-
using PackageArchiveReader reader = new PackageArchiveReader(packageStream);
190-
NuspecReader nuspec = reader.NuspecReader;
191-
var file = reader.GetFiles().FirstOrDefault(f => f.EndsWith(dep.Name + ".dll"));
192-
if (file != null)
190+
if (await resource.CopyNupkgToStreamAsync(
191+
dep.Name,
192+
new NuGetVersion(dep.Version),
193+
packageStream,
194+
cache,
195+
NullLogger.Instance,
196+
CancellationToken.None))
193197
{
194-
var fileInfo = new FileInfo(file);
195-
var path = Path.Combine(tempFolder, dep.Name, fileInfo.Name);
196-
var tmp = reader.ExtractFile(file, path, NullLogger.Instance);
198+
using PackageArchiveReader reader = new PackageArchiveReader(packageStream);
199+
NuspecReader nuspec = reader.NuspecReader;
200+
var file = reader.GetFiles().FirstOrDefault(f => f.EndsWith(dep.Name + ".dll"));
201+
if (file != null)
202+
{
203+
var fileInfo = new FileInfo(file);
204+
var path = Path.Combine(tempFolder, dep.Name, fileInfo.Name);
205+
var tmp = reader.ExtractFile(file, path, NullLogger.Instance);
206+
}
197207
}
198208
}
199209
}
200210
}
211+
finally
212+
{
213+
cache.Dispose();
214+
}
215+
return tempFolder;
201216
}
202-
finally
203-
{
204-
cache.Dispose();
205-
}
206-
return tempFolder;
207-
}
208217

209-
static string? SelectSpecificVersion(string? versionRange)
210-
{
211-
if (string.IsNullOrEmpty(versionRange))
218+
public static string? SelectSpecificVersion(string? versionRange)
212219
{
213-
return null;
214-
}
220+
if (string.IsNullOrEmpty(versionRange))
221+
{
222+
return null;
223+
}
215224

216-
var range = VersionRange.Parse(versionRange);
217-
var specificVersion = range.MinVersion;
218-
return specificVersion?.ToString();
225+
var range = VersionRange.Parse(versionRange);
226+
var specificVersion = range.MinVersion;
227+
return specificVersion?.ToString();
228+
}
219229
}

0 commit comments

Comments
 (0)