-
Notifications
You must be signed in to change notification settings - Fork 417
/
MetadataHelper.cs
156 lines (131 loc) · 6.97 KB
/
MetadataHelper.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
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using OmniSharp.Extensions;
using OmniSharp.Services;
using OmniSharp.Utilities;
namespace OmniSharp.Roslyn
{
public class MetadataHelper
{
private readonly IAssemblyLoader _loader;
private readonly Lazy<Assembly> _featureAssembly;
private readonly Lazy<Assembly> _csharpFeatureAssembly;
private readonly Lazy<Assembly> _workspaceAssembly;
private readonly Lazy<Type> _csharpMetadataAsSourceService;
private readonly Lazy<Type> _symbolKey;
private readonly Lazy<Type> _metadataAsSourceHelper;
private readonly Lazy<MethodInfo> _getLocationInGeneratedSourceAsync;
private Dictionary<string, Document> _metadataDocumentCache = new Dictionary<string, Document>();
private const string CSharpMetadataAsSourceService = "Microsoft.CodeAnalysis.CSharp.MetadataAsSource.CSharpMetadataAsSourceService";
private const string SymbolKey = "Microsoft.CodeAnalysis.SymbolKey";
private const string MetadataAsSourceHelpers = "Microsoft.CodeAnalysis.MetadataAsSource.MetadataAsSourceHelpers";
private const string GetLocationInGeneratedSourceAsync = "GetLocationInGeneratedSourceAsync";
private const string AddSourceToAsync = "AddSourceToAsync";
private const string Create = "Create";
private const string MetadataKey = "$Metadata$";
public MetadataHelper(IAssemblyLoader loader)
{
_loader = loader;
_featureAssembly = _loader.LazyLoad(Configuration.RoslynFeatures);
_csharpFeatureAssembly = _loader.LazyLoad(Configuration.RoslynCSharpFeatures);
_workspaceAssembly = _loader.LazyLoad(Configuration.RoslynWorkspaces);
_csharpMetadataAsSourceService = _csharpFeatureAssembly.LazyGetType(CSharpMetadataAsSourceService);
_symbolKey = _workspaceAssembly.LazyGetType(SymbolKey);
_metadataAsSourceHelper = _featureAssembly.LazyGetType(MetadataAsSourceHelpers);
_getLocationInGeneratedSourceAsync = _metadataAsSourceHelper.LazyGetMethod(GetLocationInGeneratedSourceAsync);
}
public Document FindDocumentInMetadataCache(string fileName)
{
if (_metadataDocumentCache.TryGetValue(fileName, out var metadataDocument))
{
return metadataDocument;
}
return null;
}
public string GetSymbolName(ISymbol symbol)
{
var topLevelSymbol = symbol.GetTopLevelContainingNamedType();
return GetTypeDisplayString(topLevelSymbol);
}
public async Task<(Document metadataDocument, string documentPath)> GetAndAddDocumentFromMetadata(Project project, ISymbol symbol, CancellationToken cancellationToken = new CancellationToken())
{
var fileName = GetFilePathForSymbol(project, symbol);
Project metadataProject;
// since submission projects cannot have new documents added to it
// we will use a separate project to hold metadata documents
if (project.IsSubmission)
{
metadataProject = project.Solution.Projects.FirstOrDefault(x => x.Name == MetadataKey);
if (metadataProject == null)
{
metadataProject = project.Solution.AddProject(MetadataKey, $"{MetadataKey}.dll", LanguageNames.CSharp)
.WithCompilationOptions(project.CompilationOptions)
.WithMetadataReferences(project.MetadataReferences);
}
}
else
{
// for regular projects we will use current project to store metadata
metadataProject = project;
}
if (!_metadataDocumentCache.TryGetValue(fileName, out var metadataDocument))
{
var topLevelSymbol = symbol.GetTopLevelContainingNamedType();
var temporaryDocument = metadataProject.AddDocument(fileName, string.Empty);
var service = _csharpMetadataAsSourceService.CreateInstance(temporaryDocument.Project.LanguageServices);
var method = _csharpMetadataAsSourceService.GetMethod(AddSourceToAsync);
var documentTask = method.Invoke<Task<Document>>(service, new object[] { temporaryDocument, topLevelSymbol, default(CancellationToken) });
metadataDocument = await documentTask;
_metadataDocumentCache[fileName] = metadataDocument;
}
return (metadataDocument, fileName);
}
public async Task<Location> GetSymbolLocationFromMetadata(ISymbol symbol, Document metadataDocument, CancellationToken cancellationToken = new CancellationToken())
{
var symbolKeyCreateMethod = _symbolKey.GetMethod(Create, BindingFlags.Static | BindingFlags.Public);
var symboldId = symbolKeyCreateMethod.InvokeStatic(new object[] { symbol, cancellationToken });
return await _getLocationInGeneratedSourceAsync.InvokeStatic<Task<Location>>(new object[] { symboldId, metadataDocument, cancellationToken });
}
private static string GetTypeDisplayString(INamedTypeSymbol symbol)
{
if (symbol.SpecialType != SpecialType.None)
{
var specialType = symbol.SpecialType;
var name = Enum.GetName(typeof(SpecialType), symbol.SpecialType).Replace("_", ".");
return name;
}
if (symbol.IsGenericType)
{
symbol = symbol.ConstructUnboundGenericType();
}
if (symbol.IsUnboundGenericType)
{
// TODO: Is this the best to get the fully metadata name?
var parts = symbol.ToDisplayParts();
var filteredParts = parts.Where(x => x.Kind != SymbolDisplayPartKind.Punctuation).ToArray();
var typeName = new StringBuilder();
foreach (var part in filteredParts.Take(filteredParts.Length - 1))
{
typeName.Append(part.Symbol.Name);
typeName.Append(".");
}
typeName.Append(symbol.MetadataName);
return typeName.ToString();
}
return symbol.ToDisplayString();
}
private static string GetFilePathForSymbol(Project project, ISymbol symbol)
{
var topLevelSymbol = symbol.GetTopLevelContainingNamedType();
return $"$metadata$/Project/{Folderize(project.Name)}/Assembly/{Folderize(topLevelSymbol.ContainingAssembly.Name)}/Symbol/{Folderize(GetTypeDisplayString(topLevelSymbol))}.cs".Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
private static string Folderize(string path) => string.Join("/", path.Split('.'));
}
}