diff --git a/.editorconfig b/.editorconfig index 0a9e8359..575e3203 100644 --- a/.editorconfig +++ b/.editorconfig @@ -124,6 +124,7 @@ csharp_preserve_single_line_statements = true csharp_preserve_single_line_blocks = true # enable internal WPILib diagnostics dotnet_diagnostic.WPILIB1100.severity = warning +dotnet_diagnostic.WPILIB1101.severity = warning ############################### # VB Coding Conventions # ############################### diff --git a/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDetector.cs b/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDetector.cs index 6850543c..2b0a1870 100644 --- a/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDetector.cs +++ b/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDetector.cs @@ -9,7 +9,8 @@ namespace WPILib.CodeHelpers.ParameterlessStructs.Analyzer; public sealed class ParameterlessStructDetector : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create([ - ParameterlessStructDiagnostics.ParameterlessConstructorMissing + ParameterlessStructDiagnostics.WPIMathHasFieldInitializers, + ParameterlessStructDiagnostics.WPIMathMustHaveParameterlessConstructor ]); public override void Initialize(AnalysisContext context) @@ -17,10 +18,67 @@ public override void Initialize(AnalysisContext context) context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterSymbolAction(AnalyzeWPIMathStruct, SymbolKind.NamedType); + context.RegisterOperationAction(AnalyzeFieldOperation, OperationKind.FieldInitializer); context.RegisterOperationAction(AnalyzePropertyOperation, OperationKind.PropertyInitializer); } + private static bool IsWPIMathNamespace(ISymbol symbol) + { + var rootNamespace = symbol.ContainingNamespace; + if (rootNamespace == null) + { + return false; + } + while (rootNamespace != null) + { + if (rootNamespace.IsGlobalNamespace) + { + // We went too far, but this is definitly not it + return false; + } + var currentName = rootNamespace.Name; + rootNamespace = rootNamespace.ContainingNamespace; + if (rootNamespace.IsGlobalNamespace) + { + // Found the actual root namespace + if (currentName == "WPIMath") + { + break; + } + // Not what we are looking for + return false; + } + } + return true; + } + + private static void AnalyzeWPIMathStruct(SymbolAnalysisContext context) + { + // Find root namespace + if (context.Symbol is not INamedTypeSymbol symbol) + { + return; + } + if (symbol.TypeKind != TypeKind.Struct) + { + return; + } + if (!IsWPIMathNamespace(symbol)) + { + return; + } + + // If we're here, we're a WPIMath struct + var parameterless = symbol.InstanceConstructors.Where(x => x.Parameters.IsEmpty).First(); + + if (parameterless.IsImplicitlyDeclared) + { + ReportDiagnostic(context, symbol); + } + } + private static void AnalyzeFieldOperation(OperationAnalysisContext context) { var fieldInitOperation = (IFieldInitializerOperation)context.Operation; @@ -35,10 +93,8 @@ private static void AnalyzeFieldOperation(OperationAnalysisContext context) 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) + // If we're here, we have an initializer, check to see if we're in WPIMath + if (IsWPIMathNamespace(field.ContainingType)) { ReportDiagnostic(context, fieldInitOperation); } @@ -58,10 +114,8 @@ private static void AnalyzePropertyOperation(OperationAnalysisContext context) 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) + // If we're here, we have an initializer, check to see if we're in WPIMath + if (IsWPIMathNamespace(property.ContainingType)) { ReportDiagnostic(context, propertyInitOperation); } @@ -69,6 +123,14 @@ private static void AnalyzePropertyOperation(OperationAnalysisContext context) private static void ReportDiagnostic(OperationAnalysisContext context, IOperation symbol) { - context.ReportDiagnostic(Diagnostic.Create(ParameterlessStructDiagnostics.ParameterlessConstructorMissing, symbol.Syntax.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(ParameterlessStructDiagnostics.WPIMathHasFieldInitializers, symbol.Syntax.GetLocation())); + } + + private static void ReportDiagnostic(SymbolAnalysisContext context, ISymbol symbol) + { + foreach (var location in symbol.Locations) + { + context.ReportDiagnostic(Diagnostic.Create(ParameterlessStructDiagnostics.WPIMathMustHaveParameterlessConstructor, location)); + } } } diff --git a/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDiagnostics.cs b/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDiagnostics.cs index 73a1555a..60218a86 100644 --- a/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDiagnostics.cs +++ b/codehelp/CodeHelpers/ParameterlessStructs/Analyzer/ParameterlessStructDiagnostics.cs @@ -11,11 +11,14 @@ public static class ParameterlessStructDiagnostics public class Ids { public const string Prefix = "WPILIB"; - public const string ParameterlessConstructorMissing = Prefix + "1100"; + public const string WPIMathHasFieldInitializers = Prefix + "1100"; + public const string WPIMathMustHaveParameterlessConstructor = Prefix + "1101"; } 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); + public static readonly DiagnosticDescriptor WPIMathHasFieldInitializers = new( + Ids.WPIMathHasFieldInitializers, "WPIMath struct has field initializers", "All structs in WPIMath must not have field or property initializers", Category, DiagnosticSeverity.Warning, false); + public static readonly DiagnosticDescriptor WPIMathMustHaveParameterlessConstructor = new( + Ids.WPIMathMustHaveParameterlessConstructor, "WPIMath struct does not have explicit parameterless constructor", "All structs in WPIMath must have explicit parameterless constructors", Category, DiagnosticSeverity.Warning, false); } diff --git a/src/wpimath/Geometry/Pose2d.cs b/src/wpimath/Geometry/Pose2d.cs index 15d555c2..f96217be 100644 --- a/src/wpimath/Geometry/Pose2d.cs +++ b/src/wpimath/Geometry/Pose2d.cs @@ -82,6 +82,10 @@ public partial class Pose2dJsonContext : JsonSerializerContext [JsonIgnore] public Length Y => Translation.Y; + public Pose2d() : this(new(), new()) + { + + } [JsonConstructor] public Pose2d(Translation2d translation, Rotation2d rotation) diff --git a/src/wpimath/Geometry/Rotation2d.cs b/src/wpimath/Geometry/Rotation2d.cs index 535079ab..0935a33d 100644 --- a/src/wpimath/Geometry/Rotation2d.cs +++ b/src/wpimath/Geometry/Rotation2d.cs @@ -100,16 +100,17 @@ internal Rotation2d(double radians) : this(radians.Radians()) [JsonPropertyName("radians")] internal double Radians => Angle.Radians; - public Rotation2d() { + public Rotation2d() : this(0.Radians()) + { } [JsonIgnore] - public Angle Angle { get; } = 0.Radians(); + public Angle Angle { get; } [JsonIgnore] - public double Cos { get; } = 1; + public double Cos { get; } [JsonIgnore] - public double Sin { get; } = 0; + public double Sin { get; } [JsonIgnore] public double Tan => Sin / Cos; diff --git a/src/wpimath/Geometry/Rotation3d.cs b/src/wpimath/Geometry/Rotation3d.cs index b73e3df4..3835a5d8 100644 --- a/src/wpimath/Geometry/Rotation3d.cs +++ b/src/wpimath/Geometry/Rotation3d.cs @@ -77,6 +77,11 @@ public partial class Rotation3dJsonContext : JsonSerializerContext [JsonPropertyName("quaternion")] public Quaternion Quaternion { get; init; } + public Rotation3d() : this(new()) + { + + } + [JsonConstructor] public Rotation3d(Quaternion quaternion) { diff --git a/src/wpimath/Geometry/Transform2d.cs b/src/wpimath/Geometry/Transform2d.cs index e34e6dbe..0ffb6e5d 100644 --- a/src/wpimath/Geometry/Transform2d.cs +++ b/src/wpimath/Geometry/Transform2d.cs @@ -81,6 +81,11 @@ public partial class Transform2dJsonContext : JsonSerializerContext public static Transform2d AdditiveIdentity => new(); + public Transform2d() : this(new Translation2d(), new Rotation2d()) + { + + } + [JsonConstructor] public Transform2d(Translation2d translation, Rotation2d rotation) { diff --git a/src/wpimath/Geometry/Translation2d.cs b/src/wpimath/Geometry/Translation2d.cs index aa707757..7bde38fc 100644 --- a/src/wpimath/Geometry/Translation2d.cs +++ b/src/wpimath/Geometry/Translation2d.cs @@ -90,6 +90,10 @@ internal Translation2d(double x, double y) [JsonPropertyName("y")] internal double JsonY => Y.Meters; + public Translation2d() : this(0.Meters(), 0.Meters()) + { + } + public Translation2d(Length x, Length y) { X = x; diff --git a/src/wpimath/Geometry/Twist2d.cs b/src/wpimath/Geometry/Twist2d.cs index 6c474c9d..4c9e8497 100644 --- a/src/wpimath/Geometry/Twist2d.cs +++ b/src/wpimath/Geometry/Twist2d.cs @@ -96,6 +96,10 @@ internal Twist2d(double dx, double dy, double dtheta) Dtheta = dtheta.Radians(); } + public Twist2d() : this(0.Meters(), 0.Meters(), 0.Radians()) + { + } + public Twist2d(Length dx, Length dy, Angle dtheta) { Dx = dx; diff --git a/src/wpimath/Geometry/Twist3d.cs b/src/wpimath/Geometry/Twist3d.cs index 3823a7b3..1bfd1494 100644 --- a/src/wpimath/Geometry/Twist3d.cs +++ b/src/wpimath/Geometry/Twist3d.cs @@ -126,6 +126,10 @@ internal Twist3d(double dx, double dy, double dz, double rx, double ry, double r Rz = rz.Radians(); } + public Twist3d() : this(0.Meters(), 0.Meters(), 0.Meters(), 0.Radians(), 0.Radians(), 0.Radians()) + { + } + public Twist3d(Length dx, Length dy, Length dz, Angle rx, Angle ry, Angle rz) { Dx = dx;