diff --git a/Common.ruleset b/Common.ruleset index c743a52470..5b8d7aa745 100644 --- a/Common.ruleset +++ b/Common.ruleset @@ -28,6 +28,9 @@ + + + diff --git a/ExternalProjects/BizHawk.Analyzer/FirstOrDefaultOnStructAnalyzer.cs b/ExternalProjects/BizHawk.Analyzer/FirstOrDefaultOnStructAnalyzer.cs new file mode 100644 index 0000000000..e68f46f01e --- /dev/null +++ b/ExternalProjects/BizHawk.Analyzer/FirstOrDefaultOnStructAnalyzer.cs @@ -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 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()) + { + 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); + }); + } +} diff --git a/References/BizHawk.Analyzer.dll b/References/BizHawk.Analyzer.dll index 54b9ce45fa..aac0933a98 100644 Binary files a/References/BizHawk.Analyzer.dll and b/References/BizHawk.Analyzer.dll differ diff --git a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs index 44a57e5055..18bf6294f3 100644 --- a/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/HotkeyConfig.cs @@ -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) diff --git a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs index 1e79cbc52a..a768bd584b 100644 --- a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs +++ b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs @@ -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(); - 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(); diff --git a/src/BizHawk.Common/HawkFile/HawkFile.cs b/src/BizHawk.Common/HawkFile/HawkFile.cs index 33ffdabe4d..3c15b49dfc 100644 --- a/src/BizHawk.Common/HawkFile/HawkFile.cs +++ b/src/BizHawk.Common/HawkFile/HawkFile.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using BizHawk.Common.CollectionExtensions; + namespace BizHawk.Common { /// @@ -257,7 +259,8 @@ namespace BizHawk.Common } /// finds an ArchiveItem with the specified name (path) within the archive; returns null if it doesnt exist - public HawkArchiveFileItem? FindArchiveMember(string? name) => ArchiveItems.FirstOrDefault(ai => ai.Name == name); + public HawkArchiveFileItem? FindArchiveMember(string? name) + => ArchiveItems.FirstOrNull(ai => ai.Name == name); /// a stream for the currently bound file /// no stream bound (haven't called or overload) diff --git a/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs b/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs index 3f4900eccf..ddace068a9 100644 --- a/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs +++ b/src/BizHawk.Emulation.DiscSystem/DiscFormats/CUE/CUE_Compile.cs @@ -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})"; } }