Skip to content

Commit 1b2881d

Browse files
authored
Diagnostics using source generator (#975)
* Add ResolveData and ToStringRepresentation * Update formatting * Don't store Diagnostics * Update * Fix * Create DiagnosticTemplateBuilder * Add source generator template * Use Verify.SourceGenerators * Create source generator WIP WIP * Use Verify.SourceGenerators * Fix
1 parent 596d997 commit 1b2881d

File tree

45 files changed

+1355
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1355
-89
lines changed

.editorconfig

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
root = true
2+
13
[*]
24
charset = utf-8
35
end_of_line = lf
@@ -23,7 +25,7 @@ indent_size = 4
2325
tab_width = 4
2426

2527
# Verify settings
26-
[*.{received,verified}.{txt,xml,json}]
28+
[*.{received,verified}.{txt,xml,json,cs}]
2729
charset = "utf-8-bom"
2830
end_of_line = lf
2931
indent_size =

.globalconfig

+6
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ dotnet_diagnostic.CS8524.severity = none
1111

1212
# Enums should not have duplicate values
1313
dotnet_diagnostic.CA1069.severity = error
14+
15+
# Non-constant fields should not be visible
16+
dotnet_diagnostic.CA2211.severity = error
17+
18+
# Don't call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
19+
dotnet_diagnostic.CA2021.severity = error

Directory.Packages.props

+7-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@
5858
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
5959
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.1" />
6060
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
61+
62+
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4"/>
63+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0"/>
64+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0"/>
65+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1"/>
6166
</ItemGroup>
6267
<ItemGroup>
6368
<!-- Testing -->
@@ -85,6 +90,7 @@
8590
</PackageVersion>
8691
<PackageVersion Include="Verify.Xunit" Version="23.1.0" />
8792
<PackageVersion Include="Verify.ImageMagick" Version="3.2.2" />
93+
<PackageVersion Include="Verify.SourceGenerators" Version="2.2.0" />
8894
<PackageVersion Include="xunit" Version="2.6.6" />
8995
<PackageVersion Include="Xunit.DependencyInjection" Version="8.9.1" />
9096
<PackageVersion Include="Xunit.DependencyInjection.Logging" Version="8.1.0" />
@@ -114,4 +120,4 @@
114120
<PackageVersion Include="Splat.Microsoft.Extensions.Logging" Version="14.8.12" />
115121
<PackageVersion Include="TransparentValueObjects" Version="1.0.1" />
116122
</ItemGroup>
117-
</Project>
123+
</Project>

NexusMods.App.sln

+21
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.Load
205205
EndProject
206206
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.Abstractions.GameLocators", "src\Abstractions\NexusMods.Abstractions.GameLocators\NexusMods.Abstractions.GameLocators.csproj", "{4334BE5B-C476-415D-B4FA-183723C4D4D5}"
207207
EndProject
208+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.App.Generators.Diagnostics", "src\NexusMods.App.Generators.Diagnostics\NexusMods.App.Generators.Diagnostics\NexusMods.App.Generators.Diagnostics.csproj", "{76F37CC8-994C-4A43-AB99-C1618FF83272}"
209+
EndProject
210+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.App.Generators.Diagnostics.Sample", "src\NexusMods.App.Generators.Diagnostics\NexusMods.App.Generators.Diagnostics.Sample\NexusMods.App.Generators.Diagnostics.Sample.csproj", "{6AE7F1F0-3E5A-42D9-BFF9-2B8F76410F4C}"
211+
EndProject
212+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NexusMods.App.Generators.Diagnostics.Tests", "tests\NexusMods.App.Generators.Diagnostics.Tests\NexusMods.App.Generators.Diagnostics.Tests.csproj", "{D497C3DF-C84C-460A-88B9-DD3D129387CC}"
213+
EndProject
208214
Global
209215
GlobalSection(SolutionConfigurationPlatforms) = preSolution
210216
Debug|Any CPU = Debug|Any CPU
@@ -527,6 +533,18 @@ Global
527533
{4334BE5B-C476-415D-B4FA-183723C4D4D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
528534
{4334BE5B-C476-415D-B4FA-183723C4D4D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
529535
{4334BE5B-C476-415D-B4FA-183723C4D4D5}.Release|Any CPU.Build.0 = Release|Any CPU
536+
{76F37CC8-994C-4A43-AB99-C1618FF83272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
537+
{76F37CC8-994C-4A43-AB99-C1618FF83272}.Debug|Any CPU.Build.0 = Debug|Any CPU
538+
{76F37CC8-994C-4A43-AB99-C1618FF83272}.Release|Any CPU.ActiveCfg = Release|Any CPU
539+
{76F37CC8-994C-4A43-AB99-C1618FF83272}.Release|Any CPU.Build.0 = Release|Any CPU
540+
{6AE7F1F0-3E5A-42D9-BFF9-2B8F76410F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
541+
{6AE7F1F0-3E5A-42D9-BFF9-2B8F76410F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
542+
{6AE7F1F0-3E5A-42D9-BFF9-2B8F76410F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
543+
{6AE7F1F0-3E5A-42D9-BFF9-2B8F76410F4C}.Release|Any CPU.Build.0 = Release|Any CPU
544+
{D497C3DF-C84C-460A-88B9-DD3D129387CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
545+
{D497C3DF-C84C-460A-88B9-DD3D129387CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
546+
{D497C3DF-C84C-460A-88B9-DD3D129387CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
547+
{D497C3DF-C84C-460A-88B9-DD3D129387CC}.Release|Any CPU.Build.0 = Release|Any CPU
530548
EndGlobalSection
531549
GlobalSection(SolutionProperties) = preSolution
532550
HideSolutionNode = FALSE
@@ -620,6 +638,9 @@ Global
620638
{83843BDF-870C-462E-95E4-8AE305E22B68} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
621639
{77E545C9-64BF-4CA0-9F24-A2DA64C268FB} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
622640
{4334BE5B-C476-415D-B4FA-183723C4D4D5} = {0CB73565-1207-4A56-A79F-6A8E9BBD795C}
641+
{76F37CC8-994C-4A43-AB99-C1618FF83272} = {E7BAE287-D505-4D6D-A090-665A64309B2D}
642+
{6AE7F1F0-3E5A-42D9-BFF9-2B8F76410F4C} = {E7BAE287-D505-4D6D-A090-665A64309B2D}
643+
{D497C3DF-C84C-460A-88B9-DD3D129387CC} = {52AF9D62-7D5B-4AD0-BA12-86F2AA67428B}
623644
EndGlobalSection
624645
GlobalSection(ExtensibilityGlobals) = postSolution
625646
SolutionGuid = {9F9F8352-34DD-42C0-8564-EE9AF34A3501}

src/Abstractions/NexusMods.Abstractions.FileExtractor/Signatures.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public enum FileType
113113
public static class Definitions {
114114

115115

116-
public static (FileType, byte[])[] Signatures = {
116+
public static readonly (FileType, byte[])[] Signatures = {
117117
// 7-Zip compressed file
118118
(FileType._7Z, new byte[] {0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C}),
119119

@@ -180,7 +180,7 @@ public static (FileType, byte[])[] Signatures = {
180180

181181
};
182182

183-
public static (FileType, Extension)[] Extensions = {
183+
public static readonly (FileType, Extension)[] Extensions = {
184184
// Ini Configuration File
185185
(FileType.INI, new Extension(".ini")),
186186

src/Abstractions/NexusMods.Abstractions.FileExtractor/Signatures.tt

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ namespace NexusMods.Abstractions.FileExtractor {
6666
public static class Definitions {
6767

6868

69-
public static (FileType, byte[])[] Signatures = {
69+
public static readonly (FileType, byte[])[] Signatures = {
7070
<#
7171
foreach (var row in magicRows)
7272
{
@@ -80,7 +80,7 @@ namespace NexusMods.Abstractions.FileExtractor {
8080

8181
};
8282

83-
public static (FileType, Extension)[] Extensions = {
83+
public static readonly (FileType, Extension)[] Extensions = {
8484
<#
8585
foreach (var row in mundaneRows)
8686
{
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
using JetBrains.Annotations;
22
using NexusMods.Abstractions.Diagnostics.References;
3-
using NexusMods.Abstractions.Serialization.Attributes;
4-
using NexusMods.Abstractions.Serialization.DataModel;
53

64
namespace NexusMods.Abstractions.Diagnostics;
75

86
/// <summary>
97
/// Represents a diagnostic.
108
/// </summary>
119
[PublicAPI]
12-
[JsonName("NexusMods.Abstractions.Diagnostics.Diagnostic")]
13-
public record Diagnostic : Entity
10+
public record Diagnostic
1411
{
1512
/// <summary>
1613
/// Gets the identifier of the diagnostic.
@@ -33,25 +30,21 @@ public record Diagnostic : Entity
3330
/// <summary>
3431
/// Gets all data references.
3532
/// </summary>
36-
public required IReadOnlyList<IDataReference> DataReferences { get; init; }
33+
public required Dictionary<DataReferenceDescription, IDataReference> DataReferences { get; init; }
3734

3835
/// <summary>
3936
/// Gets the creation time of this diagnostics.
4037
/// </summary>
4138
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
39+
}
4240

43-
/// <inheritdoc/>
44-
public override EntityCategory Category => EntityCategory.Diagnostics;
45-
46-
/// <inheritdoc/>
47-
public virtual bool Equals(Diagnostic? other)
48-
{
49-
return other is not null && DataStoreId.Equals(other.DataStoreId);
50-
}
51-
52-
/// <inheritdoc/>
53-
public override int GetHashCode()
54-
{
55-
return DataStoreId.GetHashCode();
56-
}
41+
/// <summary>
42+
/// Diagnostic with message data.
43+
/// </summary>
44+
public record Diagnostic<TMessageData> : Diagnostic where TMessageData : struct
45+
{
46+
/// <summary>
47+
/// Gets the message data.
48+
/// </summary>
49+
public required TMessageData MessageData { get; init; }
5750
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#define DiagnosticTemplateBuilderExperimentTestOutput
2+
3+
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
5+
using JetBrains.Annotations;
6+
using NexusMods.Abstractions.Diagnostics.References;
7+
8+
namespace NexusMods.Abstractions.Diagnostics;
9+
10+
/// <summary>
11+
/// A builder used at compile time by a source generator to create a diagnostic template.
12+
/// </summary>
13+
/// <remarks>
14+
/// This should never be used at runtime!
15+
/// </remarks>
16+
/// <seealso cref="IDiagnosticTemplate"/>
17+
[PublicAPI]
18+
public static class DiagnosticTemplateBuilder
19+
{
20+
/// <summary>
21+
/// Start of the builder chain.
22+
/// </summary>
23+
public static IWithIdStep Start() => ThrowException();
24+
25+
[DoesNotReturn]
26+
[ContractAnnotation("=> halt")]
27+
private static IWithIdStep ThrowException()
28+
{
29+
throw new UnreachableException($"The {nameof(DiagnosticTemplateBuilder)} is not supposed to be used at runtime!");
30+
}
31+
32+
/// <summary>
33+
/// ID step.
34+
/// </summary>
35+
public interface IWithIdStep
36+
{
37+
/// <summary>
38+
/// Sets the <see cref="DiagnosticId"/>.
39+
/// </summary>
40+
IWithSeverityStep WithId(DiagnosticId id);
41+
}
42+
43+
/// <summary>
44+
/// Severity Step.
45+
/// </summary>
46+
public interface IWithSeverityStep
47+
{
48+
/// <summary>
49+
/// Sets the <see cref="DiagnosticSeverity"/>.
50+
/// </summary>
51+
IWithMessageStep WithSeverity(DiagnosticSeverity severity);
52+
}
53+
54+
/// <summary>
55+
/// Message Step.
56+
/// </summary>
57+
public interface IWithMessageStep
58+
{
59+
/// <summary>
60+
/// Sets the message template.
61+
/// </summary>
62+
IFinishStep WithMessage(string message, Func<IMessageBuilder, IMessageBuilder> messageBuilder);
63+
}
64+
65+
/// <summary>
66+
/// Message Builder.
67+
/// </summary>
68+
public interface IMessageBuilder
69+
{
70+
/// <summary>
71+
/// Adds data references to the message.
72+
/// </summary>
73+
IMessageBuilder AddDataReference<T>(string name) where T : IDataReference;
74+
}
75+
76+
/// <summary>
77+
/// Finish step.
78+
/// </summary>
79+
public interface IFinishStep
80+
{
81+
/// <summary>
82+
/// Ends the builder.
83+
/// </summary>
84+
IDiagnosticTemplate Finish();
85+
}
86+
}
87+
88+
/// <summary>
89+
/// Output of <see cref="DiagnosticTemplateBuilder"/>.
90+
/// </summary>
91+
/// <seealso cref="DiagnosticTemplateBuilder"/>
92+
public interface IDiagnosticTemplate;
93+
94+
#if DiagnosticTemplateBuilderExperimentTestOutput
95+
#if RELEASE
96+
#else
97+
98+
[SuppressMessage("ReSharper", "UnusedType.Global")]
99+
[SuppressMessage("ReSharper", "UnusedMember.Local")]
100+
internal partial class Test
101+
{
102+
private static readonly IDiagnosticTemplate Diagnostic1Template = DiagnosticTemplateBuilder
103+
.Start()
104+
.WithId(new DiagnosticId(source: "MyCoolSource", number: 13))
105+
.WithSeverity(DiagnosticSeverity.Warning)
106+
.WithMessage("Mod '{Mod}' is not working!", messageBuilder => messageBuilder
107+
.AddDataReference<ModReference>("Mod")
108+
)
109+
.Finish();
110+
}
111+
112+
[SuppressMessage("ReSharper", "UnusedType.Global")]
113+
[SuppressMessage("ReSharper", "UnusedMember.Global")]
114+
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
115+
internal partial class Test
116+
{
117+
public static Diagnostic<Diagnostic1MessageData> CreateDiagnostic1(ModReference mod)
118+
{
119+
var messageData = new Diagnostic1MessageData(mod);
120+
121+
return new Diagnostic<Diagnostic1MessageData>
122+
{
123+
Id = new DiagnosticId(source: "MyCoolSource", number: 13),
124+
Severity = DiagnosticSeverity.Warning,
125+
Message = DiagnosticMessage.From("Mod '{Mod}' is not working!"),
126+
MessageData = messageData,
127+
DataReferences = new Dictionary<DataReferenceDescription, IDataReference>
128+
{
129+
{ DataReferenceDescription.From("Mod"), messageData.Mod },
130+
},
131+
};
132+
}
133+
134+
public readonly struct Diagnostic1MessageData
135+
{
136+
public readonly ModReference Mod;
137+
138+
public Diagnostic1MessageData(ModReference mod)
139+
{
140+
Mod = mod;
141+
}
142+
}
143+
}
144+
#endif
145+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using JetBrains.Annotations;
2+
using TransparentValueObjects;
3+
4+
namespace NexusMods.Abstractions.Diagnostics.References;
5+
6+
/// <summary>
7+
/// Represents the description of a <see cref="IDataReference"/> instance.
8+
/// </summary>
9+
[ValueObject<string>]
10+
[PublicAPI]
11+
public readonly partial struct DataReferenceDescription
12+
{
13+
/// <summary>
14+
/// Loadout.
15+
/// </summary>
16+
public static readonly DataReferenceDescription Loadout = From("Loadout");
17+
18+
/// <summary>
19+
/// Mod.
20+
/// </summary>
21+
public static readonly DataReferenceDescription Mod = From("Mod");
22+
}

0 commit comments

Comments
 (0)