Skip to content

Commit

Permalink
Add analyzer to ensure struct have a valid parameterless constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
ThadHouse committed Feb 29, 2024
1 parent ff48b82 commit 38f6736
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
# enable internal WPILib diagnostics
dotnet_diagnostic.WPILIB1100.severity = warning
###############################
# VB Coding Conventions #
###############################
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace WPILib.CodeHelpers.ParameterlessStructs.Analyzer;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ParameterlessStructDetector : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create([
ParameterlessStructDiagnostics.ParameterlessConstructorMissing
]);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

context.RegisterOperationAction(AnalyzeFieldOperation, OperationKind.FieldInitializer);
context.RegisterOperationAction(AnalyzePropertyOperation, OperationKind.PropertyInitializer);
}

private static void AnalyzeFieldOperation(OperationAnalysisContext context)
{
var fieldInitOperation = (IFieldInitializerOperation)context.Operation;
IFieldSymbol? field = fieldInitOperation.InitializedFields.FirstOrDefault();
if (field is null || field.IsConst || field.IsStatic || fieldInitOperation.Value is null)
{
return;
}

if (field.ContainingType?.TypeKind != TypeKind.Struct)
{
return;
}

// If we're here, we have an initializer, check to see if we have a default parameterless constructor
var parameterless = field.ContainingType.InstanceConstructors.Where(x => x.Parameters.IsEmpty).First();

if (parameterless.IsImplicitlyDeclared)
{
ReportDiagnostic(context, fieldInitOperation);
}
}

private static void AnalyzePropertyOperation(OperationAnalysisContext context)
{
var propertyInitOperation = (IPropertyInitializerOperation)context.Operation;
IPropertySymbol? property = propertyInitOperation.InitializedProperties.FirstOrDefault();
if (property is null || property.IsStatic || propertyInitOperation.Value is null)
{
return;
}

if (property.ContainingType?.TypeKind != TypeKind.Struct)
{
return;
}

// If we're here, we have an initializer, check to see if we have a default parameterless constructor
var parameterless = property.ContainingType.InstanceConstructors.Where(x => x.Parameters.IsEmpty).First();

if (parameterless.IsImplicitlyDeclared)
{
ReportDiagnostic(context, propertyInitOperation);
}
}

private static void ReportDiagnostic(OperationAnalysisContext context, IOperation symbol)
{
context.ReportDiagnostic(Diagnostic.Create(ParameterlessStructDiagnostics.ParameterlessConstructorMissing, symbol.Syntax.GetLocation()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace WPILib.CodeHelpers.ParameterlessStructs.Analyzer;

#pragma warning disable RS2008 // Enable analyzer release tracking

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using WPILib.CodeHelpers;

public static class ParameterlessStructDiagnostics
{
public class Ids
{
public const string Prefix = "WPILIB";
public const string ParameterlessConstructorMissing = Prefix + "1100";
}

private const string Category = "ParameterlessStructs";

public static readonly DiagnosticDescriptor ParameterlessConstructorMissing = new(
Ids.ParameterlessConstructorMissing, "Struct has field initializers but no parameterless constructor", "Struct has field initializers but no parameterless constructor", Category, DiagnosticSeverity.Warning, false);
}
5 changes: 5 additions & 0 deletions src/cscore/VideoEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public readonly struct VideoEvent(in VideoEventMarshaller.NativeCsEvent csEvent)
public EventKind Kind { get; } = csEvent.kind;
public CsListener Listener { get; } = new CsListener(csEvent.listener);

public VideoEvent() : this(default)
{

}

public static unsafe void FreeArray(VideoEventMarshaller.NativeCsEvent* array, int len)
{
CsNative.FreeEvents(array, len);
Expand Down
4 changes: 4 additions & 0 deletions src/wpimath/Geometry/Rotation2d.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ internal Rotation2d(double radians) : this(radians.Radians())
[JsonPropertyName("radians")]
internal double Radians => Angle.Radians;

public Rotation2d() {

}

[JsonIgnore]
public Angle Angle { get; } = 0.Radians();
[JsonIgnore]
Expand Down
5 changes: 5 additions & 0 deletions src/wpiutil/Marshal/WPIStringMarshaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ public unsafe struct WpiStringNative(byte* str, nuint len) : INativeArrayFree<Wp
public Ptr<byte> Str = str;
public nuint Len = len;

public WpiStringNative() : this(null, 0)
{

}

public static void FreeArray(WpiStringNative* array, int len)
{
StringsNative.FreeStringArray(array, (nuint)len);
Expand Down
5 changes: 5 additions & 0 deletions src/wpiutil/Serialization/Struct/Parsing/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ public ref struct Lexer(ReadOnlySpan<byte> inStr)
{
private Utf8CodePointEnumerator m_enumerator = new(inStr);

public Lexer() : this(default)
{

}

public readonly int Position => m_enumerator.CurrentMark;

public TokenKind Scan()
Expand Down
5 changes: 5 additions & 0 deletions src/wpiutil/Serialization/Struct/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ public ref struct Parser(ReadOnlySpan<byte> inStr)
private Lexer m_lexer = new(inStr);
private TokenKind m_token;

public Parser() : this(default)
{

}

public ParsedSchema Parse()
{
ParsedSchema schema = new ParsedSchema();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ namespace WPIUtil.Serialization.Struct.Parsing;

public ref struct Utf8CodePointEnumerator(ReadOnlySpan<byte> str)
{
public Utf8CodePointEnumerator() : this(default)
{

}

private readonly ReadOnlySpan<byte> m_str = str;
private int m_index;
public Rune Current { readonly get; private set; } = Rune.ReplacementChar;
Expand Down
5 changes: 5 additions & 0 deletions src/wpiutil/Serialization/Struct/StructPacker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public ref struct StructPacker(Span<byte> data)
private readonly Span<byte> m_data = data;
private int m_length = 0;

public StructPacker() : this(default)
{

}

public readonly ReadOnlySpan<byte> Filled => m_data[..m_length];
public readonly Span<byte> Remaining => m_data[m_length..];

Expand Down
5 changes: 5 additions & 0 deletions src/wpiutil/Serialization/Struct/StructUnpacker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public ref struct StructUnpacker(ReadOnlySpan<byte> data)
private readonly ReadOnlySpan<byte> m_data = data;
private int m_length = 0;

public StructUnpacker() : this(default)
{

}

public readonly ReadOnlySpan<byte> Used => m_data[..m_length];
public readonly ReadOnlySpan<byte> Remaining => m_data[m_length..];

Expand Down

0 comments on commit 38f6736

Please sign in to comment.