Add Analyzer rule to warn of `FirstOrDefault` on list of structs

This commit is contained in:
YoshiRulz 2022-07-28 03:05:27 +10:00
parent 8453c0e44d
commit 140e340a8d
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
7 changed files with 70 additions and 10 deletions

View File

@ -28,6 +28,9 @@
<!-- Don't call typeof(T).ToString(), use nameof operator or typeof(T).FullName --> <!-- Don't call typeof(T).ToString(), use nameof operator or typeof(T).FullName -->
<Rule Id="BHI1103" Action="Error" /> <Rule Id="BHI1103" Action="Error" />
<!-- Call to FirstOrDefault when elements are of a value type; FirstOrNull may have been intended -->
<Rule Id="BHI3100" Action="Error" />
<!-- Throw NotImplementedException from methods/props marked [FeatureNotImplemented] --> <!-- Throw NotImplementedException from methods/props marked [FeatureNotImplemented] -->
<Rule Id="BHI3300" Action="Error" /> <Rule Id="BHI3300" Action="Error" />
</Rules> </Rules>

View File

@ -0,0 +1,51 @@
namespace BizHawk.Analyzers;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class FirstOrDefaultOnStructAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor DiagUseFirstOrNull = new(
id: "BHI3100",
title: "Call to FirstOrDefault when elements are of a value type; FirstOrNull may have been intended",
messageFormat: "Call to FirstOrDefault when elements are of a value type; did you mean FirstOrNull?",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagUseFirstOrNull);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(initContext =>
{
if (initContext.Compilation.GetTypeByMetadataName("BizHawk.Common.CollectionExtensions.CollectionExtensions") is null) return; // project does not have BizHawk.Common dependency
var linqExtClassSym = initContext.Compilation.GetTypeByMetadataName("System.Linq.Enumerable")!;
IMethodSymbol? firstOrDefaultNoPredSym = null;
IMethodSymbol? firstOrDefaultWithPredSym = null;
foreach (var sym in linqExtClassSym.GetMembers("FirstOrDefault").Cast<IMethodSymbol>())
{
if (sym.Parameters.Length is 2) firstOrDefaultWithPredSym = sym;
else firstOrDefaultNoPredSym = sym;
}
initContext.RegisterOperationAction(
oac =>
{
var operation = (IInvocationOperation) oac.Operation;
var calledSym = operation.TargetMethod.ConstructedFrom;
if (!(firstOrDefaultWithPredSym!.Matches(calledSym) || firstOrDefaultNoPredSym!.Matches(calledSym))) return;
var receiverExprType = (INamedTypeSymbol) operation.SemanticModel.GetTypeInfo((CSharpSyntaxNode) operation.Arguments[0].Syntax)!.ConvertedType!;
if (receiverExprType.TypeArguments[0].IsValueType) oac.ReportDiagnostic(Diagnostic.Create(DiagUseFirstOrNull, operation.Syntax.GetLocation()));
},
OperationKind.Invocation);
});
}
}

Binary file not shown.

View File

@ -6,6 +6,7 @@ using System.Windows.Forms;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
@ -197,7 +198,7 @@ namespace BizHawk.Client.EmuHawk
{ {
if (e.IsPressed(Keys.Enter) || e.IsPressed(Keys.Tab)) if (e.IsPressed(Keys.Enter) || e.IsPressed(Keys.Tab))
{ {
var k = HotkeyInfo.AllHotkeys.FirstOrDefault(kvp => string.Compare(kvp.Value.DisplayName, SearchBox.Text, true) is 0).Key; var k = HotkeyInfo.AllHotkeys.FirstOrNull(kvp => string.Compare(kvp.Value.DisplayName, SearchBox.Text, true) is 0)?.Key;
// Found // Found
if (k is not null) if (k is not null)

View File

@ -16,6 +16,7 @@ using BizHawk.Emulation.Common;
using BizHawk.Client.Common; using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk.Properties; using BizHawk.Client.EmuHawk.Properties;
using BizHawk.Client.EmuHawk.ToolExtensions; using BizHawk.Client.EmuHawk.ToolExtensions;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Client.EmuHawk namespace BizHawk.Client.EmuHawk
{ {
@ -242,13 +243,13 @@ namespace BizHawk.Client.EmuHawk
{ {
if (_textTable.Any()) if (_textTable.Any())
{ {
var byteArr = new List<byte>(); var byteArr = new byte[str.Length];
foreach (var chr in str) for (var i = 0; i < str.Length; i++)
{ {
byteArr.Add((byte)_textTable.FirstOrDefault(kvp => kvp.Value == chr).Key); var c = str[i];
byteArr[i] = (byte) (_textTable.FirstOrNull(kvp => kvp.Value == c)?.Key ?? 0);
} }
return byteArr;
return byteArr.ToArray();
} }
return str.Select(Convert.ToByte).ToArray(); return str.Select(Convert.ToByte).ToArray();

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using BizHawk.Common.CollectionExtensions;
namespace BizHawk.Common namespace BizHawk.Common
{ {
/// <summary> /// <summary>
@ -257,7 +259,8 @@ namespace BizHawk.Common
} }
/// <summary>finds an ArchiveItem with the specified name (path) within the archive; returns null if it doesnt exist</summary> /// <summary>finds an ArchiveItem with the specified name (path) within the archive; returns null if it doesnt exist</summary>
public HawkArchiveFileItem? FindArchiveMember(string? name) => ArchiveItems.FirstOrDefault(ai => ai.Name == name); public HawkArchiveFileItem? FindArchiveMember(string? name)
=> ArchiveItems.FirstOrNull(ai => ai.Name == name);
/// <returns>a stream for the currently bound file</returns> /// <returns>a stream for the currently bound file</returns>
/// <exception cref="InvalidOperationException">no stream bound (haven't called <see cref="BindArchiveMember(int)"/> or overload)</exception> /// <exception cref="InvalidOperationException">no stream bound (haven't called <see cref="BindArchiveMember(int)"/> or overload)</exception>

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Common.CollectionExtensions;
//this would be a good place for structural validation //this would be a good place for structural validation
//after this step, we won't want to have to do stuff like that (it will gunk up already sticky code) //after this step, we won't want to have to do stuff like that (it will gunk up already sticky code)
@ -109,10 +110,10 @@ namespace BizHawk.Emulation.DiscSystem.CUE
public override string ToString() public override string ToString()
{ {
var idx = Indexes.FirstOrDefault(cci => cci.Number == 1); var idx = Indexes.FirstOrNull(static cci => cci.Number is 1);
if (idx.Number != 1) return $"T#{Number:D2} NO INDEX 1"; if (idx is null) return $"T#{Number:D2} NO INDEX 1";
var indexlist = string.Join("|", Indexes); var indexlist = string.Join("|", Indexes);
return $"T#{Number:D2} {BlobIndex}:{idx.FileMSF} ({indexlist})"; return $"T#{Number:D2} {BlobIndex}:{idx.Value.FileMSF} ({indexlist})";
} }
} }