Skip to content

Commit

Permalink
Add analyzer to ensure public API doesn't use Action or Func
Browse files Browse the repository at this point in the history
  • Loading branch information
ThadHouse committed Feb 22, 2024
1 parent 97b434c commit 93a36a6
Show file tree
Hide file tree
Showing 21 changed files with 269 additions and 189 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,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
7 changes: 7 additions & 0 deletions WPILib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WPILib.CodeHelpers", "codeh
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeHelpers.Test", "codehelp\CodeHelpers.Test\CodeHelpers.Test.csproj", "{42E0EFC6-4990-4395-A9D1-8683778751E7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "roboRIODev", "dev\roboRIODev\roboRIODev.csproj", "{CC242C8A-93D4-4083-9C3C-D98A1FE687C1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -112,6 +114,10 @@ Global
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42E0EFC6-4990-4395-A9D1-8683778751E7}.Release|Any CPU.Build.0 = Release|Any CPU
{CC242C8A-93D4-4083-9C3C-D98A1FE687C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC242C8A-93D4-4083-9C3C-D98A1FE687C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC242C8A-93D4-4083-9C3C-D98A1FE687C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC242C8A-93D4-4083-9C3C-D98A1FE687C1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8F38C25E-641E-47FC-AC0A-0717F2159E8F} = {DB664556-4BF0-4874-8CB6-DC24E60A67AF}
Expand All @@ -130,5 +136,6 @@ Global
{630D08FD-CD06-4674-BC5A-F1F211619E83} = {AD95ECD8-E708-4FB4-9B7E-A8A8EF3FCB3E}
{76F4D0AE-2123-493B-B721-4118330C52BB} = {909FC1DB-3083-4F01-8496-B8C9DD4FEA13}
{42E0EFC6-4990-4395-A9D1-8683778751E7} = {909FC1DB-3083-4F01-8496-B8C9DD4FEA13}
{CC242C8A-93D4-4083-9C3C-D98A1FE687C1} = {1CA9AB3B-4828-4F07-8C0E-88EF7C5A9ACD}
EndGlobalSection
EndGlobal
108 changes: 108 additions & 0 deletions codehelp/CodeHelpers/ActionFuncDetector/Analyzer/ActionFuncDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace WPILib.CodeHelpers.ActionFuncDetector.Analyzer;

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

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
context.RegisterSymbolAction(AnalyzeProperty, SymbolKind.Property);
context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
}

private static void AnalyzeMethod(SymbolAnalysisContext context)
{
if (context.Symbol is not IMethodSymbol methodSymbol)
{
return;
}

if (methodSymbol.DeclaredAccessibility != Accessibility.Public)
{
return;
}

if (TypeCheck(methodSymbol.ReturnType))
{
ReportDiagnostic(context, methodSymbol.ReturnType);
}

foreach (var parameter in methodSymbol.Parameters)
{
if (TypeCheck(parameter.Type))
{
ReportDiagnostic(context, parameter);
}
}
}

private static void ReportDiagnostic(SymbolAnalysisContext context, ISymbol symbol)
{
foreach (var location in symbol.Locations)
{
context.ReportDiagnostic(Diagnostic.Create(ActionFuncDiagnostics.UseOfActionFunc, location));
}
}

private static void AnalyzeProperty(SymbolAnalysisContext context)
{
if (context.Symbol is not IPropertySymbol propertySymbol)
{
return;
}

if (propertySymbol.DeclaredAccessibility != Accessibility.Public)
{
return;
}

if (TypeCheck(propertySymbol.Type))
{
ReportDiagnostic(context, propertySymbol.Type);
}
}

private static bool TypeCheck(ITypeSymbol typeSymbol)
{
var containingNamespace = typeSymbol.ContainingNamespace;
if (containingNamespace is null)
{
return false;
}
if (containingNamespace.Name != "System")
{
return false;
}
var name = typeSymbol.Name;
return name == "Action" || name == "Func";
}

private static void AnalyzeField(SymbolAnalysisContext context)
{
if (context.Symbol is not IFieldSymbol fieldSymbol)
{
return;
}

if (fieldSymbol.DeclaredAccessibility != Accessibility.Public)
{
return;
}

if (TypeCheck(fieldSymbol.Type))
{
ReportDiagnostic(context, fieldSymbol.Type);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace WPILib.CodeHelpers.ActionFuncDetector.Analyzer;

#pragma warning disable RS2008 // Enable analyzer release tracking

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

public static class ActionFuncDiagnostics
{
public class Ids
{
public const string Prefix = "WPILIB";
public const string UseOfActionFunc = Prefix + "1100";
}

private const string Category = "AllowedPublicTypes";

public static readonly DiagnosticDescriptor UseOfActionFunc = new(
Ids.UseOfActionFunc, "Action/Func is used in a public API", "Use of Action or Func in a public API is disallowed", Category, DiagnosticSeverity.Warning, false);
}
20 changes: 15 additions & 5 deletions dev/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
<Project>
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\hal\hal.csproj" />
<ProjectReference Include="..\..\src\ntcore\ntcore.csproj" />
<ProjectReference Include="..\..\src\wpilibsharp\wpilibsharp.csproj" />
<ProjectReference Include="..\..\src\wpiutil\wpiutil.csproj" />
</ItemGroup>
</Project>
2 changes: 0 additions & 2 deletions dev/desktopDev/desktopDev.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>


Expand Down
128 changes: 14 additions & 114 deletions dev/roboRIODev/Program.cs
Original file line number Diff line number Diff line change
@@ -1,123 +1,23 @@
using System;
using System.Runtime.CompilerServices;
using WPILib;
using WPILib.Counters;
using WPILib.SmartDashboardNS;

namespace roboRIODev
namespace TestRobot;

internal class Program
{
public class Robot : TimedRobot
private static void Main(string[] args)
{
//private TimeSpan lastTime = Timer.FPGATimestamp;
//double[] buffer = new double[50];
//int idx = 0;

//private int can = CANAPI.Initialize(CANManufacturer.kTeamUse, 1, CANDeviceType.kMiscellaneous);

private readonly PWMSparkMax sparkMax = new PWMSparkMax(0);
private readonly DigitalInput di = new DigitalInput(0);
private Tachometer absoluteTach;
private readonly DigitalInput quadDI = new DigitalInput(1);
private readonly DigitalInput quadDIB = new DigitalInput(2);
private Tachometer quadratureTach;
private UpDownCounter quadratureCounter;

private ExternalDirectionCounter externalDirectionCounter;
private ExternalDirectionCounter externalDirectionCounter2x;

AsynchronousInterrupt interrupt;

int count = 0;

public override void RobotInit()
{
absoluteTach = new Tachometer(di);
quadratureTach = new Tachometer(quadDI);
quadratureTach.EdgesPerRevolution = 2048;
quadratureCounter = new UpDownCounter();
quadratureCounter.UpSource = quadDI;
quadratureCounter.UpEdgeConfiguration = EdgeConfiguration.kRisingEdge;

externalDirectionCounter = new ExternalDirectionCounter(quadDI, quadDIB);

externalDirectionCounter2x = new ExternalDirectionCounter(quadDI, quadDIB);
externalDirectionCounter2x.EdgeConfiguration = EdgeConfiguration.kBothEdges;
externalDirectionCounter2x.ReverseDirection = true;

DigitalGlitchFilter filter = new DigitalGlitchFilter();
filter.SetPeriod(TimeSpan.FromSeconds(1));
//filter.Add(di);

interrupt = new AsynchronousInterrupt(di, (r, f) =>
{
Console.WriteLine("Interrupt Occured " + count);
count++;
//Thread.Sleep(20);
});

interrupt.SetInterruptEdges(true, false);

interrupt.Enable();
}



public override unsafe void RobotPeriodic()
{
SmartDashboard.PutNumber("External Dir Tach", externalDirectionCounter.Count);

SmartDashboard.PutNumber("External Dir Tach 2x", externalDirectionCounter2x.Count);


SmartDashboard.PutNumber("Absolute Tach", absoluteTach.RotationalSpeed?.RevolutionsPerSecond ?? double.MaxValue);

SmartDashboard.PutNumber("Quadrature Tach", quadratureTach.RotationalSpeed?.RevolutionsPerSecond ?? 0);

SmartDashboard.PutNumber("Quadrature Counter", quadratureCounter.Count);

SmartDashboard.PutNumber("Joystick", DriverStation.Instance.GetStickAxis(0, 1));
sparkMax.Set(DriverStation.Instance.GetStickAxis(0, 1));

if (DriverStation.Instance.GetStickButton(0, 1))
{
Console.WriteLine("Disable");
interrupt.Disable();
}

if (DriverStation.Instance.GetStickButton(0, 2))
{
Console.WriteLine("Enable");
interrupt.Enable();
}
//sparkMax.SetVoltage(ElectricPotential.FromVolts(5));
//int idxLocal = idx;
//try
//{
// CANAPI.WritePacket(can, new Span<byte>(&idxLocal, 4), 42);
//}
//catch (UncleanStatusException ex)
//{
// ;
//}

//var current = Timer.FPGATimestamp;
//var delta = current - lastTime;
//lastTime = current;
//buffer[idx] = delta.TotalMilliseconds;
//idx++;
//if (idx == 50)
//{
// Console.WriteLine(buffer.Average());
// idx = 0;
//}
//base.RobotPeriodic();
}
RobotRunner.StartRobot<Robot>();
}
}

class Program
internal static class RobotInitializer
{
[ModuleInitializer]
public static void Initialize()
{
static void Main(string[] args)
{
RobotBase.StartRobot<Robot>();
}
// Force load the HAL, because we can't guarantee it won't be
// accidentally used too early later
RobotRunner.InitializeHAL();
}
}
8 changes: 8 additions & 0 deletions dev/roboRIODev/Robot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using WPILib;

namespace TestRobot;

public class Robot : RobotBase
{

}
28 changes: 8 additions & 20 deletions dev/roboRIODev/roboRIODev.csproj
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Packable>false</Packable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\hal\hal.csproj" />
<!--<ProjectReference Include="..\..\src\newcommands\newcommands.csproj" />-->
<ProjectReference Include="..\..\src\ntcore\ntcore.csproj" />
<!--<ProjectReference Include="..\..\src\thirdparty\oblog\oblog.csproj" />
<ProjectReference Include="..\..\src\thirdparty\SparkMax\SparkMax.csproj" />-->
<ProjectReference Include="..\..\src\wpilibsharp\wpilibsharp.csproj" />
<ProjectReference Include="..\..\src\wpiutil\wpiutil.csproj" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>TestRobot</RootNamespace>
</PropertyGroup>

</Project>
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@

<ItemGroup>
<PackageReference Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="All"/>
<ProjectReference Include="$(SolutionDir)codehelp\CodeHelpers\WPILib.CodeHelpers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
Loading

0 comments on commit 93a36a6

Please sign in to comment.