Skip to content

Commit 5be304e

Browse files
committed
Big start on the epilogue source generator
1 parent e5623ab commit 5be304e

15 files changed

+1205
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
namespace CodeHelpers.Test.LogGenerator;
2+
3+
using System.Text;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp.Testing;
6+
using Microsoft.CodeAnalysis.Testing;
7+
using Microsoft.CodeAnalysis.Text;
8+
using Stereologue;
9+
using WPILib.CodeHelpers.LogGenerator.SourceGenerator;
10+
11+
public class EpilogueGeneratorTest
12+
{
13+
[Theory]
14+
[InlineData("bool", "LogBoolean")]
15+
[InlineData("int", "LogInteger")]
16+
[InlineData("long", "LogInteger")]
17+
public async Task TestPrimitives(string type, string output)
18+
{
19+
string testString = @"
20+
using Epilogue;
21+
22+
[Logged]
23+
public partial class MyNewClass
24+
{
25+
[Logged]
26+
public REPLACEME Variable() { return default; }
27+
}
28+
";
29+
30+
string expected = @"partial class MyNewClass
31+
: global::Stereologue.ILogged
32+
{
33+
public void UpdateStereologue(string path, global::Stereologue.Stereologuer logger)
34+
{
35+
logger.REPLACEME($""{path}/Variable"", global::Stereologue.LogType.File | global::Stereologue.LogType.Nt, Variable(), global::Stereologue.LogLevel.Default);
36+
}
37+
}
38+
";
39+
testString = testString.Replace("REPLACEME", type);
40+
41+
// Due to StringBuilder in the source generator,
42+
// We must normalize the output line endings
43+
expected = expected.NormalizeLineEndings();
44+
expected = expected.Replace("REPLACEME", output);
45+
46+
await new CSharpSourceGeneratorTest<LogGeneratorSharp, DefaultVerifier>()
47+
{
48+
TestState = {
49+
AdditionalReferences = {
50+
typeof(LogAttribute).Assembly
51+
},
52+
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
53+
Sources = {
54+
testString,
55+
},
56+
AnalyzerConfigFiles = {
57+
("/.editorconfig", SourceText.From(TestHelpers.EditorConfig, Encoding.UTF8))
58+
},
59+
GeneratedSources = {
60+
($"WPILib.CodeHelpers{Path.DirectorySeparatorChar}WPILib.CodeHelpers.EpilogueGenerator.SourceGenerator.EpilogueGeneratorSharp{Path.DirectorySeparatorChar}MyNewClass.g.cs", SourceText.From(expected, Encoding.UTF8))
61+
},
62+
},
63+
}.RunAsync();
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
4+
namespace WPILib.CodeHelpers.EpilogueGenerator;
5+
6+
public record CustomLoggerType(TypeDeclarationModel TypeDeclarations, EquatableArray<TypeDeclarationModel> SupportedTypes);
7+
8+
internal static class CustomLoggerTypeExtensions
9+
{
10+
public static CustomLoggerType GetCustomLoggerType(this ImmutableArray<AttributeData> attributes, INamedTypeSymbol symbol, CancellationToken token)
11+
{
12+
token.ThrowIfCancellationRequested();
13+
14+
var loggableTypes = ImmutableArray.CreateBuilder<TypeDeclarationModel>(1);
15+
16+
foreach (var attribute in attributes)
17+
{
18+
token.ThrowIfCancellationRequested();
19+
foreach (var named in attribute.NamedArguments)
20+
{
21+
token.ThrowIfCancellationRequested();
22+
if (named.Key == "Types")
23+
{
24+
if (!named.Value.IsNull && named.Value.Kind is TypedConstantKind.Array)
25+
{
26+
foreach (var value in named.Value.Values)
27+
{
28+
token.ThrowIfCancellationRequested();
29+
if (!value.IsNull && value.Kind is TypedConstantKind.Type && value.Value is INamedTypeSymbol typeFor)
30+
{
31+
loggableTypes.Add(typeFor.GetTypeDeclarationModel());
32+
}
33+
}
34+
}
35+
}
36+
}
37+
}
38+
39+
token.ThrowIfCancellationRequested();
40+
41+
return new(symbol.GetTypeDeclarationModel(), loggableTypes.ToImmutable());
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace WPILib.CodeHelpers.EpilogueGenerator;
2+
3+
public enum FailureMode
4+
{
5+
None,
6+
AttributeUnknownMemberType,
7+
ProtobufArray,
8+
UnknownTypeNonArray,
9+
UnknownTypeArray,
10+
MethodReturnsVoid,
11+
MethodHasParameters,
12+
UnknownTypeToLog,
13+
NullableStructArray,
14+
MissingGenerateLog,
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
using System.Collections.Immutable;
2+
using System.Text;
3+
using Epilogue;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.PooledObjects;
7+
8+
namespace WPILib.CodeHelpers.EpilogueGenerator;
9+
10+
// Contains all information about a [Logged] attribute
11+
public record LogAttributeInfo(string? Name, LogStrategy LogStrategy, LogImportance LogImportance)
12+
{
13+
// public string GetLogStrageyString(LanguageKind language)
14+
// {
15+
// if (language == LanguageKind.CSharp)
16+
// {
17+
// return AllLevelValues[(int)LogLevel];
18+
// }
19+
// else if (language == LanguageKind.VisualBasic)
20+
// {
21+
// return AllLevelValuesVb[(int)LogLevel];
22+
// }
23+
// return "";
24+
// }
25+
26+
// public string GetLogTypeString(LanguageKind language)
27+
// {
28+
// if (language == LanguageKind.CSharp)
29+
// {
30+
// return AllTypeValues[(int)LogType];
31+
// }
32+
// else if (language == LanguageKind.VisualBasic)
33+
// {
34+
// return AllTypeValuesVb[(int)LogType];
35+
// }
36+
// return "";
37+
// }
38+
39+
// private static ImmutableList<string> GetAllLevelValues()
40+
// {
41+
// LogLevel[] allLogLevels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
42+
// var builder = ImmutableList.CreateBuilder<string>();
43+
// string fullName = typeof(LogLevel).FullName;
44+
// string rootName = $"global::{fullName}.";
45+
// foreach (var i in allLogLevels)
46+
// {
47+
// builder.Add($"{rootName}{i}");
48+
// }
49+
// return builder.ToImmutable();
50+
// }
51+
52+
// private static ImmutableList<string> GetAllTypeValues()
53+
// {
54+
// LogType[] allLogTypes = (LogType[])Enum.GetValues(typeof(LogType));
55+
// var builder = ImmutableList.CreateBuilder<string>();
56+
// LogType baseLog = LogType.None;
57+
// foreach (var i in allLogTypes)
58+
// {
59+
// baseLog |= i;
60+
// }
61+
// int permutations = (int)baseLog;
62+
// permutations += 1;
63+
// string fullName = typeof(LogType).FullName;
64+
// string rootName = $"global::{fullName}.";
65+
// builder.Add($"{rootName}{nameof(LogType.None)}");
66+
// StringBuilder stringBuilder = new();
67+
// for (int i = 1; i < permutations; i++)
68+
// {
69+
// LogType type = (LogType)i;
70+
71+
// if ((type & LogType.File) != 0)
72+
// {
73+
// if (stringBuilder.Length != 0)
74+
// {
75+
// stringBuilder.Append(" | ");
76+
// }
77+
// stringBuilder.Append($"{rootName}{nameof(LogType.File)}");
78+
// }
79+
// if ((type & LogType.Nt) != 0)
80+
// {
81+
// if (stringBuilder.Length != 0)
82+
// {
83+
// stringBuilder.Append(" | ");
84+
// }
85+
// stringBuilder.Append($"{rootName}{nameof(LogType.Nt)}");
86+
// }
87+
// if ((type & LogType.Once) != 0)
88+
// {
89+
// if (stringBuilder.Length != 0)
90+
// {
91+
// stringBuilder.Append(" | ");
92+
// }
93+
// stringBuilder.Append($"{rootName}{nameof(LogType.Once)}");
94+
// }
95+
// builder.Add(stringBuilder.ToString());
96+
// stringBuilder.Clear();
97+
// }
98+
99+
// return builder.ToImmutable();
100+
// }
101+
102+
// private static ImmutableList<string> GetAllLevelValuesVb()
103+
// {
104+
// LogLevel[] allLogLevels = (LogLevel[])Enum.GetValues(typeof(LogLevel));
105+
// var builder = ImmutableList.CreateBuilder<string>();
106+
// string fullName = typeof(LogLevel).FullName;
107+
// string rootName = $"Global.{fullName}.";
108+
// foreach (var i in allLogLevels)
109+
// {
110+
// builder.Add($"{rootName}{i}");
111+
// }
112+
// return builder.ToImmutable();
113+
// }
114+
115+
// private static ImmutableList<string> GetAllTypeValuesVb()
116+
// {
117+
// LogType[] allLogTypes = (LogType[])Enum.GetValues(typeof(LogType));
118+
// var builder = ImmutableList.CreateBuilder<string>();
119+
// LogType baseLog = LogType.None;
120+
// foreach (var i in allLogTypes)
121+
// {
122+
// baseLog |= i;
123+
// }
124+
// int permutations = (int)baseLog;
125+
// permutations += 1;
126+
// string fullName = typeof(LogType).FullName;
127+
// string rootName = $"Global.{fullName}.";
128+
// builder.Add($"{rootName}{nameof(LogType.None)}");
129+
// StringBuilder stringBuilder = new();
130+
// for (int i = 1; i < permutations; i++)
131+
// {
132+
// LogType type = (LogType)i;
133+
134+
// if ((type & LogType.File) != 0)
135+
// {
136+
// if (stringBuilder.Length != 0)
137+
// {
138+
// stringBuilder.Append(" Or ");
139+
// }
140+
// stringBuilder.Append($"{rootName}{nameof(LogType.File)}");
141+
// }
142+
// if ((type & LogType.Nt) != 0)
143+
// {
144+
// if (stringBuilder.Length != 0)
145+
// {
146+
// stringBuilder.Append(" Or ");
147+
// }
148+
// stringBuilder.Append($"{rootName}{nameof(LogType.Nt)}");
149+
// }
150+
// if ((type & LogType.Once) != 0)
151+
// {
152+
// if (stringBuilder.Length != 0)
153+
// {
154+
// stringBuilder.Append(" Or ");
155+
// }
156+
// stringBuilder.Append($"{rootName}{nameof(LogType.Once)}");
157+
// }
158+
// builder.Add(stringBuilder.ToString());
159+
// stringBuilder.Clear();
160+
// }
161+
162+
// return builder.ToImmutable();
163+
// }
164+
165+
// public static ImmutableList<string> AllTypeValues { get; } = GetAllTypeValues();
166+
167+
// public static ImmutableList<string> AllLevelValues { get; } = GetAllLevelValues();
168+
169+
// public static ImmutableList<string> AllTypeValuesVb { get; } = GetAllTypeValuesVb();
170+
171+
// public static ImmutableList<string> AllLevelValuesVb { get; } = GetAllLevelValuesVb();
172+
}
173+
174+
internal static class LogAttributeInfoExtensions
175+
{
176+
public static LogAttributeInfo? ToAttributeInfo(this AttributeData attributeData, INamedTypeSymbol? attributeClass, CancellationToken token, out bool notLogged)
177+
{
178+
if (attributeClass is null)
179+
{
180+
notLogged = false;
181+
return null;
182+
}
183+
184+
if (attributeClass.IsNotLoggedAttributeClass())
185+
{
186+
notLogged = true;
187+
return null;
188+
}
189+
notLogged = false;
190+
if (attributeClass.IsLoggedAttributeClass())
191+
{
192+
token.ThrowIfCancellationRequested();
193+
194+
string? path = null;
195+
LogStrategy logStrategyEnum = LogStrategyExtensions.DefaultLogStrategy;
196+
LogImportance logImportanceEnum = LogImportanceExtensions.DefaultLogImportance;
197+
198+
// Get the log attribute
199+
foreach (var named in attributeData.NamedArguments)
200+
{
201+
if (named.Key == "Name")
202+
{
203+
if (!named.Value.IsNull)
204+
{
205+
path = SymbolDisplay.FormatPrimitive(named.Value.Value!, false, false);
206+
}
207+
token.ThrowIfCancellationRequested();
208+
}
209+
else if (named.Key == "Strategy")
210+
{
211+
// A boxed primitive can be unboxed to an enum with the same underlying type.
212+
logStrategyEnum = (LogStrategy)named.Value.Value!;
213+
token.ThrowIfCancellationRequested();
214+
}
215+
else if (named.Key == "Importance")
216+
{
217+
// A boxed primitive can be unboxed to an enum with the same underlying type.
218+
logImportanceEnum = (LogImportance)named.Value.Value!;
219+
token.ThrowIfCancellationRequested();
220+
}
221+
}
222+
223+
return new LogAttributeInfo(path, logStrategyEnum, logImportanceEnum);
224+
}
225+
return null;
226+
}
227+
228+
229+
}

0 commit comments

Comments
 (0)