Add Analyzer rule to warn of `FirstOrDefault` on list of structs
This commit is contained in:
parent
8453c0e44d
commit
140e340a8d
|
@ -28,6 +28,9 @@
|
|||
<!-- Don't call typeof(T).ToString(), use nameof operator or typeof(T).FullName -->
|
||||
<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] -->
|
||||
<Rule Id="BHI3300" Action="Error" />
|
||||
</Rules>
|
||||
|
|
|
@ -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.
|
@ -6,6 +6,7 @@ using System.Windows.Forms;
|
|||
|
||||
using BizHawk.Client.Common;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
|
@ -197,7 +198,7 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
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
|
||||
if (k is not null)
|
||||
|
|
|
@ -16,6 +16,7 @@ using BizHawk.Emulation.Common;
|
|||
using BizHawk.Client.Common;
|
||||
using BizHawk.Client.EmuHawk.Properties;
|
||||
using BizHawk.Client.EmuHawk.ToolExtensions;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
namespace BizHawk.Client.EmuHawk
|
||||
{
|
||||
|
@ -242,13 +243,13 @@ namespace BizHawk.Client.EmuHawk
|
|||
{
|
||||
if (_textTable.Any())
|
||||
{
|
||||
var byteArr = new List<byte>();
|
||||
foreach (var chr in str)
|
||||
var byteArr = new byte[str.Length];
|
||||
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.ToArray();
|
||||
return byteArr;
|
||||
}
|
||||
|
||||
return str.Select(Convert.ToByte).ToArray();
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
namespace BizHawk.Common
|
||||
{
|
||||
/// <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>
|
||||
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>
|
||||
/// <exception cref="InvalidOperationException">no stream bound (haven't called <see cref="BindArchiveMember(int)"/> or overload)</exception>
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Common.CollectionExtensions;
|
||||
|
||||
//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)
|
||||
|
@ -109,10 +110,10 @@ namespace BizHawk.Emulation.DiscSystem.CUE
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
var idx = Indexes.FirstOrDefault(cci => cci.Number == 1);
|
||||
if (idx.Number != 1) return $"T#{Number:D2} NO INDEX 1";
|
||||
var idx = Indexes.FirstOrNull(static cci => cci.Number is 1);
|
||||
if (idx is null) return $"T#{Number:D2} NO INDEX 1";
|
||||
var indexlist = string.Join("|", Indexes);
|
||||
return $"T#{Number:D2} {BlobIndex}:{idx.FileMSF} ({indexlist})";
|
||||
return $"T#{Number:D2} {BlobIndex}:{idx.Value.FileMSF} ({indexlist})";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue