-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathVsctGuidsGenerator.cs
More file actions
206 lines (173 loc) · 7.45 KB
/
VsctGuidsGenerator.cs
File metadata and controls
206 lines (173 loc) · 7.45 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace CodingWithCalvin.VsixSdk.Generators;
/// <summary>
/// Source generator that creates static classes with GUIDs and IDs from VSCT files.
/// </summary>
[Generator]
public class VsctGuidsGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Find all .vsct files in AdditionalFiles
var vsctFiles = context.AdditionalTextsProvider
.Where(file => file.Path.EndsWith(".vsct", StringComparison.OrdinalIgnoreCase));
// Combine with compilation to get namespace
var compilationAndVscts = context.CompilationProvider
.Combine(vsctFiles.Collect());
context.RegisterSourceOutput(compilationAndVscts, (ctx, source) =>
{
var (compilation, vscts) = source;
foreach (var vsct in vscts)
{
GenerateVsctGuids(ctx, compilation, vsct);
}
});
}
private static void GenerateVsctGuids(
SourceProductionContext context,
Compilation compilation,
AdditionalText vsctFile)
{
var text = vsctFile.GetText(context.CancellationToken);
if (text == null) return;
var fileName = Path.GetFileNameWithoutExtension(vsctFile.Path);
var className = $"{fileName}Vsct";
try
{
var doc = new XmlDocument();
doc.LoadXml(text.ToString());
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("vsct", "http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable");
// Extract all GuidSymbols
var guidSymbols = ExtractGuidSymbols(doc, nsmgr);
if (guidSymbols.Count == 0) return;
// Get namespace from compilation
var rootNamespace = compilation.AssemblyName ?? "GeneratedCode";
var source = GenerateSource(rootNamespace, className, fileName, guidSymbols);
context.AddSource($"{className}.g.cs", SourceText.From(source, Encoding.UTF8));
}
catch (Exception ex)
{
// Report diagnostic on error
var descriptor = new DiagnosticDescriptor(
"VSIXSDK002",
"Failed to parse VSCT file",
"Failed to parse VSCT file '{0}': {1}",
"VsixSdk",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, vsctFile.Path, ex.Message));
}
}
private static List<GuidSymbol> ExtractGuidSymbols(XmlDocument doc, XmlNamespaceManager nsmgr)
{
var result = new List<GuidSymbol>();
// Try both namespaced and non-namespaced queries
var guidSymbolNodes = doc.SelectNodes("//vsct:GuidSymbol", nsmgr);
if (guidSymbolNodes == null || guidSymbolNodes.Count == 0)
{
guidSymbolNodes = doc.SelectNodes("//GuidSymbol");
}
if (guidSymbolNodes == null) return result;
foreach (XmlNode node in guidSymbolNodes)
{
var name = node.Attributes?["name"]?.Value;
var value = node.Attributes?["value"]?.Value;
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value))
continue;
var guidSymbol = new GuidSymbol
{
Name = name!,
Value = value!.Trim('{', '}')
};
// Extract nested IDSymbols
var idSymbolNodes = node.SelectNodes("vsct:IDSymbol", nsmgr);
if (idSymbolNodes == null || idSymbolNodes.Count == 0)
{
idSymbolNodes = node.SelectNodes("IDSymbol");
}
if (idSymbolNodes != null)
{
foreach (XmlNode idNode in idSymbolNodes)
{
var idName = idNode.Attributes?["name"]?.Value;
var idValue = idNode.Attributes?["value"]?.Value;
if (!string.IsNullOrEmpty(idName) && !string.IsNullOrEmpty(idValue))
{
guidSymbol.IdSymbols.Add(new IdSymbol { Name = idName!, Value = idValue! });
}
}
}
result.Add(guidSymbol);
}
return result;
}
private static string GenerateSource(string rootNamespace, string className, string fileName, List<GuidSymbol> guidSymbols)
{
var sb = new StringBuilder();
sb.AppendLine("//------------------------------------------------------------------------------");
sb.AppendLine("// <auto-generated>");
sb.AppendLine("// This code was generated by CodingWithCalvin.VsixSdk from the VSCT file.");
sb.AppendLine("// Changes to this file may cause incorrect behavior and will be lost if");
sb.AppendLine("// the code is regenerated.");
sb.AppendLine("// </auto-generated>");
sb.AppendLine("//------------------------------------------------------------------------------");
sb.AppendLine();
sb.AppendLine("using System;");
sb.AppendLine();
sb.AppendLine($"namespace {rootNamespace}");
sb.AppendLine("{");
sb.AppendLine(" /// <summary>");
sb.AppendLine($" /// GUIDs and IDs from {fileName}.vsct");
sb.AppendLine(" /// </summary>");
sb.AppendLine($" internal static class {className}");
sb.AppendLine(" {");
foreach (var guidSymbol in guidSymbols)
{
if (guidSymbol.IdSymbols.Count == 0)
{
// Simple GUID constant with both string and Guid versions
sb.AppendLine($" /// <summary>GUID: {{{guidSymbol.Value}}}</summary>");
sb.AppendLine($" public const string {guidSymbol.Name}String = \"{guidSymbol.Value}\";");
sb.AppendLine($" public static readonly Guid {guidSymbol.Name} = new Guid({guidSymbol.Name}String);");
sb.AppendLine();
}
else
{
// Nested class with GUID and IDs
sb.AppendLine($" /// <summary>Command set GUID: {{{guidSymbol.Value}}}</summary>");
sb.AppendLine($" public static class {guidSymbol.Name}");
sb.AppendLine(" {");
sb.AppendLine($" public const string GuidString = \"{guidSymbol.Value}\";");
sb.AppendLine($" public static readonly Guid Guid = new Guid(GuidString);");
foreach (var idSymbol in guidSymbol.IdSymbols)
{
sb.AppendLine($" public const int {idSymbol.Name} = {idSymbol.Value};");
}
sb.AppendLine(" }");
sb.AppendLine();
}
}
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
private class GuidSymbol
{
public string Name { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
public List<IdSymbol> IdSymbols { get; } = new List<IdSymbol>();
}
private class IdSymbol
{
public string Name { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
}