Note: the following code ignores nested types.
Without further ado, here is the code:
using ManyConsole;
using Microsoft.Build.Evaluation;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CSTool
{
class TypeItem
{
public string TypeName { get; set; }
public string FilePath { get; set; }
}
class TypeMapItem
{
public string AssemblyName { get; set; }
public IEnumerable<TypeItem> Types { get; set; }
}
class MapTypesCmd : ConsoleCommand
{
private string m_projFilePath;
public MapTypesCmd()
{
IsCommand("map-types", "Maps all the types of the given assembly.");
HasRequiredOption("p|proj=", "The project source file.", v => m_projFilePath = Path.GetFullPath(v));
}
public override int Run(string[] remainingArguments)
{
Run(m_projFilePath);
return 0;
}
public static void Run(string projFilePath)
{
var project = new Project(projFilePath);
var res = new TypeMapItem
{
AssemblyName = project.GetPropertyValue("AssemblyName"),
Types = YieldTypeItems(project)
};
var serializer = JsonSerializer.CreateDefault();
serializer.Formatting = Formatting.Indented;
using var w = new JsonTextWriter(Console.Out);
serializer.Serialize(w, res);
}
private static IEnumerable<TypeItem> YieldTypeItems(Project project)
{
var curDir = Path.GetFullPath($"{project.FullPath}\\..");
Directory.SetCurrentDirectory(curDir);
curDir += "\\";
curDir = curDir.Substring(project.GetPropertyValue("WorkspaceRoot").Length);
foreach (var compileItem in project.AllEvaluatedItems.Cast<ProjectItem>().Where(item => item.ItemType == "Compile"))
{
var filePath = compileItem.EvaluatedInclude;
var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(filePath));
var root = tree.GetCompilationUnitRoot();
foreach (var typeItem in YieldTypes(filePath, root.ChildNodes(), "").Select(t => new TypeItem
{
TypeName = t,
FilePath = curDir + filePath
}))
{
yield return typeItem;
}
}
}
private static IEnumerable<string> YieldTypes(string filePath, IEnumerable<SyntaxNode> nodes, string prefix)
{
foreach (var member in nodes.OfType<MemberDeclarationSyntax>())
{
SyntaxToken id;
TypeParameterListSyntax typeParamList = null;
if (member is DelegateDeclarationSyntax del)
{
id = del.Identifier;
typeParamList = del.TypeParameterList;
}
else if (member is TypeDeclarationSyntax type)
{
id = type.Identifier;
typeParamList = type.TypeParameterList;
}
else if (member is EnumDeclarationSyntax enumeration)
{
id = enumeration.Identifier;
}
else if (member is NamespaceDeclarationSyntax ns)
{
foreach (var t in YieldTypes(filePath, ns.ChildNodes(), prefix + ns.Name.ToString() + "."))
{
yield return t;
}
continue;
}
else
{
throw new ApplicationException($"Unexpected declaration found in {filePath} @ {member.SpanStart} - {member}");
}
var typeParamListText = "";
if (typeParamList != null)
{
typeParamListText = "<" + string.Join(",", typeParamList.Parameters.Select(o => o.Identifier.ToString())) + ">";
}
yield return prefix + id.ToString() + typeParamListText;
}
}
}
}
Here is the project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ManyConsole" Version="2.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.ComponentModel.Composition" Version="5.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Build">
<HintPath>..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Build.Framework">
<HintPath>..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.Framework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Build.Utilities.Core">
<HintPath>..\..\..\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\Microsoft.Build.Utilities.Core.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
A very important note - I could not make the MSBuild API NuGet packages work for me. My experience is described in this SO question - https://stackoverflow.com/questions/66146972/whats-the-correct-usage-of-microsoft-build-evaluation-fast-forward-to-2021. As a result I take the MSBuild DLLs directly from VS 2019 installation folder.