Skip to content

Commit ad431ae

Browse files
committed
Move CodeHelpers to their own thing, add unit test for fixup
1 parent ad32e9d commit ad431ae

20 files changed

+219
-28
lines changed

WPILib.sln

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stereologue", "src\thirdpar
3737
EndProject
3838
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "stereologue.test", "test\stereologue.test\stereologue.test.csproj", "{630D08FD-CD06-4674-BC5A-F1F211619E83}"
3939
EndProject
40-
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sourcegeneration", "sourcegeneration", "{909FC1DB-3083-4F01-8496-B8C9DD4FEA13}"
40+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "codehelp", "codehelp", "{909FC1DB-3083-4F01-8496-B8C9DD4FEA13}"
4141
EndProject
42-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StereologueSourceGenerator", "sourcegeneration\StereologueSourceGenerator\StereologueSourceGenerator.csproj", "{76F4D0AE-2123-493B-B721-4118330C52BB}"
42+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPILib.CodeHelpers", "codehelp\CodeHelpers\WPILib.CodeHelpers.csproj", "{76F4D0AE-2123-493B-B721-4118330C52BB}"
43+
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeHelpers.Test", "codehelp\CodeHelpers.Test\CodeHelpers.Test.csproj", "{42E0EFC6-4990-4395-A9D1-8683778751E7}"
4345
EndProject
4446
Global
4547
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -106,6 +108,10 @@ Global
106108
{76F4D0AE-2123-493B-B721-4118330C52BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
107109
{76F4D0AE-2123-493B-B721-4118330C52BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
108110
{76F4D0AE-2123-493B-B721-4118330C52BB}.Release|Any CPU.Build.0 = Release|Any CPU
111+
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
112+
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
113+
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
114+
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Release|Any CPU.Build.0 = Release|Any CPU
109115
EndGlobalSection
110116
GlobalSection(NestedProjects) = preSolution
111117
{8F38C25E-641E-47FC-AC0A-0717F2159E8F} = {DB664556-4BF0-4874-8CB6-DC24E60A67AF}
@@ -123,5 +129,6 @@ Global
123129
{2124D403-17C4-4116-932D-74933812ECE6} = {822627EF-820D-488B-BC14-BDC4BA88454B}
124130
{630D08FD-CD06-4674-BC5A-F1F211619E83} = {AD95ECD8-E708-4FB4-9B7E-A8A8EF3FCB3E}
125131
{76F4D0AE-2123-493B-B721-4118330C52BB} = {909FC1DB-3083-4F01-8496-B8C9DD4FEA13}
132+
{42E0EFC6-4990-4395-A9D1-8683778751E7} = {909FC1DB-3083-4F01-8496-B8C9DD4FEA13}
126133
EndGlobalSection
127134
EndGlobal
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
14+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.1" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
16+
<PackageReference Include="xunit" Version="2.4.2" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2" />
18+
<ProjectReference Include="..\CodeHelpers\WPILib.CodeHelpers.csproj"/>
19+
<ProjectReference Include="..\..\src\thirdparty\Stereologue\Stereologue.csproj" />
20+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
21+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
22+
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<Using Include="Xunit" />
27+
</ItemGroup>
28+
29+
</Project>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Testing;
3+
using Microsoft.CodeAnalysis.Testing.Verifiers;
4+
using WPILib.CodeHelpers.LogGenerator.Analyzer;
5+
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.CodeFixVerifier<WPILib.CodeHelpers.LogGenerator.Analyzer.LogGeneratorAnalyzer, WPILib.CodeHelpers.LogGenerator.CodeFixer.LogGeneratorFixer>;
6+
7+
namespace CodeHelpers.Test;
8+
9+
public class LogGeneratorFixerTest
10+
{
11+
const string InternalTypes = @"
12+
namespace Stereologue
13+
{
14+
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
15+
public sealed class LogAttribute : System.Attribute
16+
{
17+
}
18+
19+
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)]
20+
public sealed class GenerateLogAttribute : System.Attribute
21+
{
22+
}
23+
}
24+
";
25+
[Fact]
26+
public async void Test1()
27+
{
28+
string testString = @"
29+
using Stereologue;
30+
public partial class MyNewClass
31+
{
32+
[Log]
33+
public int Variable { get; }
34+
}
35+
";
36+
string fixedCode = @"
37+
using Stereologue;
38+
[GenerateLog]
39+
public partial class MyNewClass
40+
{
41+
[Log]
42+
public int Variable { get; }
43+
}";
44+
testString += InternalTypes;
45+
fixedCode += InternalTypes;
46+
var expected = Verify.Diagnostic(LoggerDiagnostics.MissingGenerateLog).WithLocation(3, 22).WithArguments(["Variable", "MyNewClass"]);
47+
await Verify.VerifyCodeFixAsync(testString, expected, fixedCode);
48+
}
49+
}

sourcegeneration/StereologueSourceGenerator/EquatableArray.cs renamed to codehelp/CodeHelpers/EquatableArray.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
using System.Runtime.CompilerServices;
1111
using System.Runtime.InteropServices;
1212

13-
namespace Stereologue.SourceGenerator;
13+
namespace WPILib.CodeHelpers;
1414

1515
/// <summary>
1616
/// Extensions for <see cref="EquatableArray{T}"/>.

sourcegeneration/StereologueSourceGenerator/HashCode.cs renamed to codehelp/CodeHelpers/HashCode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
#pragma warning disable CS0809, IDE0009, IDE1006, IDE0048
88

9-
namespace Stereologue.SourceGenerator;
9+
namespace WPILib.CodeHelpers;
1010

1111
/// <summary>
1212
/// A polyfill type that mirrors some methods from <see cref="HashCode"/> on .7.

sourcegeneration/StereologueSourceGenerator/GenerateLogAnalyzer.cs renamed to codehelp/CodeHelpers/LogGenerator/Analyzer/LogGeneratorAnalyzer.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
using Microsoft.CodeAnalysis;
33
using Microsoft.CodeAnalysis.Diagnostics;
44

5-
namespace Stereologue.SourceGenerator;
5+
namespace WPILib.CodeHelpers.LogGenerator.Analyzer;
66

77
[DiagnosticAnalyzer(LanguageNames.CSharp)]
8-
public sealed class GenerateLogAnalyzer : DiagnosticAnalyzer
8+
public sealed class LogGeneratorAnalyzer : DiagnosticAnalyzer
99
{
1010
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create([
1111
LoggerDiagnostics.UnknownMemberType,
@@ -16,14 +16,34 @@ public sealed class GenerateLogAnalyzer : DiagnosticAnalyzer
1616
LoggerDiagnostics.LoggedHasUnknownType,
1717
LoggerDiagnostics.UnknownFailureMode,
1818
LoggerDiagnostics.NullableStructArray,
19-
LoggerDiagnostics.UnknownSpecialTypeIntArray
19+
LoggerDiagnostics.UnknownSpecialTypeIntArray,
20+
LoggerDiagnostics.MissingGenerateLog,
2021
]);
2122

2223
public override void Initialize(AnalysisContext context)
2324
{
2425
context.EnableConcurrentExecution();
2526
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
2627
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
28+
context.RegisterSymbolAction(AnalyzeMembers, SymbolKind.Method | SymbolKind.Field | SymbolKind.Property);
29+
}
30+
31+
private static void AnalyzeMembers(SymbolAnalysisContext context)
32+
{
33+
if (!context.Symbol.HasLogAttribute()) {
34+
return;
35+
}
36+
37+
var containingType = context.Symbol.ContainingType;
38+
if (containingType is null) {
39+
return;
40+
}
41+
42+
if (!containingType.HasGenerateLogAttribute()) {
43+
foreach (var location in containingType.Locations) {
44+
context.ReportDiagnostic(Diagnostic.Create(LoggerDiagnostics.MissingGenerateLog, location, context.Symbol.Name, containingType.ToDisplayString()));
45+
}
46+
}
2747
}
2848

2949
private static void AnalyzeSymbol(SymbolAnalysisContext context)

sourcegeneration/StereologueSourceGenerator/LoggerDiagnostics.cs renamed to codehelp/CodeHelpers/LogGenerator/Analyzer/LoggerDiagnostics.cs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
namespace Stereologue.SourceGenerator;
1+
namespace WPILib.CodeHelpers.LogGenerator.Analyzer;
22

33
#pragma warning disable RS2008 // Enable analyzer release tracking
44

55
using System;
66
using Microsoft.CodeAnalysis;
77
using Microsoft.CodeAnalysis.Diagnostics;
8+
using WPILib.CodeHelpers;
89

910
public static class LoggerDiagnostics
1011
{
@@ -20,28 +21,31 @@ public class Ids
2021
public const string UnknownFailureMode = Prefix + "1006";
2122
public const string NullableStructArray = Prefix + "1007";
2223
public const string UnknownSpecialTypeIntArray = Prefix + "1008";
24+
public const string MissingGenerateLog = Prefix + "1008";
2325
}
2426

25-
private const string Category = "StereologueSourceGenerator";
27+
private const string Category = "SourceGeneration";
2628

2729
public static readonly DiagnosticDescriptor UnknownMemberType = new(
28-
Ids.UnknownMemberType, "Loggable member call type is unknown", "[Log] attribute cannot be applied to member {0}. Member type is unknown.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
30+
Ids.UnknownMemberType, "Loggable member call type is unknown", "[Log] attribute cannot be applied to member '{0}'. Member type is unknown.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
2931
public static readonly DiagnosticDescriptor ProtobufIsArray = new(
30-
Ids.ProtobufIsArray, "Loggable member is array of protobufs", "[Log] attribute cannot be applied to member {0}. Cannot log array of Protobufs.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
32+
Ids.ProtobufIsArray, "Loggable member is array of protobufs", "[Log] attribute cannot be applied to member '{0}'. Cannot log array of Protobufs.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
3133
public static readonly DiagnosticDescriptor UnknownSpecialTypeArray = new(
32-
Ids.UnknownSpecialTypeArray, "Loggable member has invalid type for array use", "[Log] attribute cannot be applied to member {0}. Cannot log arrays of type '{1}'.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
34+
Ids.UnknownSpecialTypeArray, "Loggable member has invalid type for array use", "[Log] attribute cannot be applied to member '{0}'. Cannot log arrays of type '{1}'.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
3335
public static readonly DiagnosticDescriptor LoggedMethodReturnsVoid = new(
34-
Ids.LoggedMethodReturnsVoid, "Loggable method returns void", "[Log] attribute cannot be applied to member {0}. Cannot log from a void returning method.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
36+
Ids.LoggedMethodReturnsVoid, "Loggable method returns void", "[Log] attribute cannot be applied to member '{0}'. Cannot log from a void returning method.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
3537
public static readonly DiagnosticDescriptor LoggedMethodTakeParameters = new(
36-
Ids.LoggedMethodTakeParameters, "Loggable method takes parameters", "[Log] attribute cannot be applied to member {0}. Cannot log from a method taking parameters.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
38+
Ids.LoggedMethodTakeParameters, "Loggable method takes parameters", "[Log] attribute cannot be applied to member '{0}'. Cannot log from a method taking parameters.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
3739
public static readonly DiagnosticDescriptor LoggedHasUnknownType = new(
38-
Ids.LoggedHasUnknownType, "Loggable member type is not loggable", "[Log] attribute cannot be applied to member {0}; cannot log type '{1}'", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
40+
Ids.LoggedHasUnknownType, "Loggable member type is not loggable", "[Log] attribute cannot be applied to member '{0}'; cannot log type '{1}'", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
3941
public static readonly DiagnosticDescriptor UnknownFailureMode = new(
4042
Ids.UnknownFailureMode, "Unknown Failure Mode", "Failure mode has no diagnostic. Report to RobotDotNet.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
4143
public static readonly DiagnosticDescriptor NullableStructArray = new(
42-
Ids.NullableStructArray, "Loggable member is array of Nullable<Struct>", "[Log] attribute cannot be applied to member {0}. Cannot log arrays of Nullable Structs.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
44+
Ids.NullableStructArray, "Loggable member is array of Nullable<Struct>", "[Log] attribute cannot be applied to member '{0}'. Cannot log arrays of Nullable Structs.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
4345
public static readonly DiagnosticDescriptor UnknownSpecialTypeIntArray = new(
44-
Ids.UnknownSpecialTypeIntArray, "Loggable member has invalid integer type for array use", "[Log] attribute cannot be applied to member {0}. Cannot log arrays of type '{1}'. Can only log arrays of 'long'.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
46+
Ids.UnknownSpecialTypeIntArray, "Loggable member has invalid integer type for array use", "[Log] attribute cannot be applied to member '{0}'. Cannot log arrays of type '{1}'. Can only log arrays of 'long'.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
47+
public static readonly DiagnosticDescriptor MissingGenerateLog = new(
48+
Ids.MissingGenerateLog, "Type has Log member but is not GenerateLog", "Member '{0}' has [Log] attribute, however containing type {1} is not attributed with [GenerateLog]. No logging will be generated for this member.", Category, DiagnosticSeverity.Error, isEnabledByDefault: true, "");
4549

4650
public static void ReportDiagnostic(this SymbolAnalysisContext context, FailureMode failureMode, ISymbol symbol)
4751
{
@@ -89,6 +93,9 @@ public static void ReportDiagnostic(this SymbolAnalysisContext context, FailureM
8993
case FailureMode.NullableStructArray:
9094
context.ReportDiagnostic(Diagnostic.Create(NullableStructArray, location, symbol.Name));
9195
break;
96+
case FailureMode.MissingGenerateLog:
97+
context.ReportDiagnostic(Diagnostic.Create(MissingGenerateLog, location, symbol.Name, symbol.ContainingType?.ToDisplayString()));
98+
break;
9299
default:
93100
context.ReportDiagnostic(Diagnostic.Create(UnknownFailureMode, location));
94101
break;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System.Collections.Immutable;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CodeActions;
4+
using Microsoft.CodeAnalysis.CodeFixes;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using WPILib.CodeHelpers.LogGenerator.Analyzer;
8+
9+
namespace WPILib.CodeHelpers.LogGenerator.CodeFixer;
10+
11+
[ExportCodeFixProvider(LanguageNames.CSharp)]
12+
public class LogGeneratorFixer : CodeFixProvider
13+
{
14+
private const string title = "Make constant";
15+
16+
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create([
17+
LoggerDiagnostics.Ids.MissingGenerateLog,
18+
]);
19+
20+
public sealed override FixAllProvider GetFixAllProvider()
21+
{
22+
return WellKnownFixAllProviders.BatchFixer;
23+
}
24+
25+
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
26+
{
27+
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
28+
29+
var diagnostic = context.Diagnostics.First();
30+
var diagnosticSpan = diagnostic.Location.SourceSpan;
31+
32+
// Find the type declaration identified by the diagnostic.
33+
var declaration = root!.FindToken(diagnosticSpan.Start).Parent!.AncestorsAndSelf().OfType<TypeDeclarationSyntax>().First();
34+
35+
// Register a code action that will invoke the fix.
36+
context.RegisterCodeFix(
37+
CodeAction.Create(
38+
title: title,
39+
createChangedDocument: c => AddGenerateLogAttribute(context.Document, declaration!, c),
40+
equivalenceKey: title),
41+
diagnostic);
42+
}
43+
44+
private async Task<Document> AddGenerateLogAttribute(Document document, TypeDeclarationSyntax typeSyntax, CancellationToken cancellationToken)
45+
{
46+
var root = await document.GetSyntaxRootAsync(cancellationToken)!;
47+
var attributes = typeSyntax.AttributeLists.Add(
48+
SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(
49+
SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("GenerateLog"))
50+
)));
51+
52+
return document.WithSyntaxRoot(
53+
root!.ReplaceNode(
54+
typeSyntax,
55+
typeSyntax.WithAttributeLists(attributes).NormalizeWhitespace()
56+
));
57+
}
58+
}

sourcegeneration/StereologueSourceGenerator/FailureMode.cs renamed to codehelp/CodeHelpers/LogGenerator/FailureMode.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace Stereologue.SourceGenerator;
1+
namespace WPILib.CodeHelpers.LogGenerator;
22

33
public enum FailureMode
44
{
@@ -11,4 +11,5 @@ public enum FailureMode
1111
MethodHasParameters,
1212
UnknownTypeToLog,
1313
NullableStructArray,
14+
MissingGenerateLog,
1415
}

sourcegeneration/StereologueSourceGenerator/LogAttributeInfo.cs renamed to codehelp/CodeHelpers/LogGenerator/LogAttributeInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CSharp;
33

4-
namespace Stereologue.SourceGenerator;
4+
namespace WPILib.CodeHelpers.LogGenerator;
55

66
// Contains all information about a [Log] attribute
77
internal record LogAttributeInfo(string? Path, string LogLevel, string LogType, bool UseProtobuf);

0 commit comments

Comments
 (0)