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 -->
|
<!-- 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>
|
||||||
|
|
|
@ -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.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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue