diff --git a/.globalconfig b/.globalconfig index 0a2727ecd4..734abab03f 100644 --- a/.globalconfig +++ b/.globalconfig @@ -46,8 +46,49 @@ dotnet_diagnostic.IDE0130.severity = warning # IDE1006: Naming style dotnet_diagnostic.IDE1006.severity = warning -#Disable operator overloads requiring alternate named methods -dotnet_diagnostic.CA2225.severity = none +# CA1305: Specify IFormatProvider +# Too many noisy warnings for parsing/formatting numbers +dotnet_diagnostic.CA1305.severity = none + +# CA1806: Do not ignore method results +# Not sure why it's performance, but causing too much noise +dotnet_diagnostic.CA1806.severity = none + +# CA1822: Mark members as static +# Potential false positive around reflection/too much noise +dotnet_diagnostic.CA1822.severity = none + +# CA1826: Do not use Enumerable methods on indexable collections +# Noise for FirstOrDefault and LastOrDefault +dotnet_diagnostic.CA1826.severity = none + +# CA1859: Use concrete types when possible for improved performance +# Involves design considerations +dotnet_diagnostic.CA1859.severity = none + +# CA1861: Avoid constant arrays as arguments +# Outdated with collection expressions +dotnet_diagnostic.CA1861.severity = none + +# CA1868: Unnecessary call to 'Contains(item)' +# Causing noises only +dotnet_diagnostic.CA1868.severity = none + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2101: Specify marshaling for P/Invoke string arguments +# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport +dotnet_diagnostic.CA2101.severity = none + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = warning + +# CA2242: Test for NaN correctly +dotnet_diagnostic.CA2242.severity = warning # Banned APIs dotnet_diagnostic.RS0030.severity = error diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index a0a86a2fe5..4f7ad3837c 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -8,8 +8,4 @@ M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. -M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. -M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. M:System.Reflection.Assembly.GetEntryAssembly();Use osu.Framework.RuntimeInfo.EntryAssembly instead diff --git a/CodeAnalysis/osu-framework.ruleset b/CodeAnalysis/osu-framework.ruleset deleted file mode 100644 index 7b87ec248a..0000000000 --- a/CodeAnalysis/osu-framework.ruleset +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index fb2120b7ad..2d79ed8366 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,7 +28,17 @@ - $(MSBuildThisFileDirectory)CodeAnalysis\osu-framework.ruleset + Default + Default + Recommended + Recommended + Recommended + Recommended + Default + Minimum + Recommended + Default + Default ppy Pty Ltd diff --git a/osu-framework.sln b/osu-framework.sln index 155884055c..2ee06d1065 100644 --- a/osu-framework.sln +++ b/osu-framework.sln @@ -31,7 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .globalconfig = .globalconfig Directory.Build.props = Directory.Build.props .config\dotnet-tools.json = .config\dotnet-tools.json - CodeAnalysis\osu-framework.ruleset = CodeAnalysis\osu-framework.ruleset + global.json = global.json osu-framework.sln.DotSettings = osu-framework.sln.DotSettings osu.Framework.Android.props = osu.Framework.Android.props osu.Framework.iOS.props = osu.Framework.iOS.props @@ -74,7 +74,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeAnalysis", "CodeAnalysis", "{50334A1F-990D-45FA-A1FE-C92F1994D97B}" ProjectSection(SolutionItems) = preProject CodeAnalysis\BannedSymbols.txt = CodeAnalysis\BannedSymbols.txt - CodeAnalysis\osu-framework.ruleset = CodeAnalysis\osu-framework.ruleset EndProjectSection EndProject Global diff --git a/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs b/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs index 36542417de..5dabc47f9b 100644 --- a/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs +++ b/osu.Framework.SourceGeneration.Tests/GeneratorTestHelper.cs @@ -23,7 +23,7 @@ public static void VerifyZeroDiagnostics(this GeneratorDriverRunResult runResult .Where(d => d.Severity == DiagnosticSeverity.Error) .ToArray(); - if (compilationDiagnostics.Any() || generatorDiagnostics.Any()) + if (compilationDiagnostics.Length > 0 || generatorDiagnostics.Length > 0) { var sb = new StringBuilder(); diff --git a/osu.Framework.Tests/Audio/AudioThreadTest.cs b/osu.Framework.Tests/Audio/AudioThreadTest.cs index dd85628fd4..5919c34050 100644 --- a/osu.Framework.Tests/Audio/AudioThreadTest.cs +++ b/osu.Framework.Tests/Audio/AudioThreadTest.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Threading; @@ -87,7 +88,7 @@ void runScheduled() => thread.Scheduler.Add(() => runScheduled(); - Task.WaitAll(cts.Task); + cts.Task.WaitSafely(); } } } diff --git a/osu.Framework.Tests/Containers/TestSceneContainerState.cs b/osu.Framework.Tests/Containers/TestSceneContainerState.cs index 6d40f21b79..9107e0508e 100644 --- a/osu.Framework.Tests/Containers/TestSceneContainerState.cs +++ b/osu.Framework.Tests/Containers/TestSceneContainerState.cs @@ -16,6 +16,8 @@ using osu.Framework.Tests.Visual; using osuTK; +#pragma warning disable CA1826 // Performance for test is not important + namespace osu.Framework.Tests.Containers { [System.ComponentModel.Description("ensure valid container state in various scenarios")] diff --git a/osu.Framework.Tests/IO/TestWebRequest.cs b/osu.Framework.Tests/IO/TestWebRequest.cs index 5dc898de18..d2207bed05 100644 --- a/osu.Framework.Tests/IO/TestWebRequest.cs +++ b/osu.Framework.Tests/IO/TestWebRequest.cs @@ -891,7 +891,9 @@ private class HttpBinPostResponse [JsonProperty("json")] public TestObject Json { get; set; } +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("form")] +#pragma warning restore CA1507 private Dictionary form { get; set; } } @@ -906,7 +908,9 @@ private class HttpBinPutResponse [JsonProperty("args")] private Dictionary arguments { get; set; } +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("form")] +#pragma warning restore CA1507 private Dictionary form { get; set; } } diff --git a/osu.Framework.Tests/Visual/UserInterface/TestSceneTabControl.cs b/osu.Framework.Tests/Visual/UserInterface/TestSceneTabControl.cs index c83d51df50..f265370e34 100644 --- a/osu.Framework.Tests/Visual/UserInterface/TestSceneTabControl.cs +++ b/osu.Framework.Tests/Visual/UserInterface/TestSceneTabControl.cs @@ -17,6 +17,8 @@ using osu.Framework.Localisation; using osuTK; +#pragma warning disable CA1826 // Performance for test is not important + namespace osu.Framework.Tests.Visual.UserInterface { public partial class TestSceneTabControl : FrameworkTestScene diff --git a/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs b/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs index 07147af650..186c6cb692 100644 --- a/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs +++ b/osu.Framework/Audio/Mixing/Bass/BassAudioMixer.cs @@ -186,7 +186,7 @@ public long ChannelGetPosition(IBassAudioChannel channel, PositionFlags mode = P /// How to set the position. /// /// If successful, then is returned, else is returned. - /// Use to get the error code. + /// Use to get the error code. /// public bool ChannelSetPosition(IBassAudioChannel channel, long position, PositionFlags mode = PositionFlags.Bytes) { @@ -222,11 +222,11 @@ public bool ChannelGetLevel(IBassAudioChannel channel, [In, Out] float[] levels, /// See: . /// The to retrieve the data of. /// float[] to write the data to. - /// Number of bytes wanted, and/or . - /// If an error occurs, -1 is returned, use to get the error code. + /// Number of bytes wanted, and/or . + /// If an error occurs, -1 is returned, use to get the error code. /// When requesting FFT data, the number of bytes read from the channel (to perform the FFT) is returned. - /// When requesting sample data, the number of bytes written to buffer will be returned (not necessarily the same as the number of bytes read when using the or DataFlags.Fixed flag). - /// When using the flag, the number of bytes in the channel's buffer is returned. + /// When requesting sample data, the number of bytes written to buffer will be returned (not necessarily the same as the number of bytes read when using the or DataFlags.Fixed flag). + /// When using the flag, the number of bytes in the channel's buffer is returned. /// public int ChannelGetData(IBassAudioChannel channel, float[] buffer, int length) => BassMix.ChannelGetData(channel.Handle, buffer, length); @@ -240,7 +240,7 @@ public int ChannelGetData(IBassAudioChannel channel, float[] buffer, int length) /// The sync parameters, depending on the sync type. /// The callback function which should be invoked with the sync. /// User instance data to pass to the callback function. - /// If successful, then the new synchroniser's handle is returned, else 0 is returned. Use to get the error code. + /// If successful, then the new synchroniser's handle is returned, else 0 is returned. Use to get the error code. public int ChannelSetSync(IBassAudioChannel channel, SyncFlags type, long parameter, SyncProcedure procedure, IntPtr user = default) => BassMix.ChannelSetSync(channel.Handle, type, parameter, procedure, user); @@ -248,8 +248,8 @@ public int ChannelSetSync(IBassAudioChannel channel, SyncFlags type, long parame /// Removes a synchroniser from a mixer source channel. /// /// The to remove the synchroniser for. - /// Handle of the synchroniser to remove (return value of a previous call). - /// If successful, is returned, else is returned. Use to get the error code. + /// Handle of the synchroniser to remove (return value of a previous call). + /// If successful, is returned, else is returned. Use to get the error code. public bool ChannelRemoveSync(IBassAudioChannel channel, int sync) => BassMix.ChannelRemoveSync(channel.Handle, sync); @@ -257,7 +257,7 @@ public bool ChannelRemoveSync(IBassAudioChannel channel, int sync) /// Frees a channel's resources. /// /// The to free. - /// If successful, is returned, else is returned. Use to get the error code. + /// If successful, is returned, else is returned. Use to get the error code. public bool StreamFree(IBassAudioChannel channel) { Remove(channel, false); diff --git a/osu.Framework/Audio/Track/Waveform.cs b/osu.Framework/Audio/Track/Waveform.cs index e38da672bc..f1f22d0f89 100644 --- a/osu.Framework/Audio/Track/Waveform.cs +++ b/osu.Framework/Audio/Track/Waveform.cs @@ -270,7 +270,7 @@ private float computeIntensity(ChannelInfo info, float[] bins, float startFreque /// An async task for the generation of the . public async Task GenerateResampledAsync(int pointCount, CancellationToken cancellationToken = default) { - if (pointCount < 0) throw new ArgumentOutOfRangeException(nameof(pointCount)); + ArgumentOutOfRangeException.ThrowIfNegative(pointCount); if (pointCount == 0) return new Waveform(null); diff --git a/osu.Framework/Graphics/Containers/FlowContainer.cs b/osu.Framework/Graphics/Containers/FlowContainer.cs index 4e78fe87e9..ab0d79287e 100644 --- a/osu.Framework/Graphics/Containers/FlowContainer.cs +++ b/osu.Framework/Graphics/Containers/FlowContainer.cs @@ -137,10 +137,10 @@ public void Insert(int position, T drawable) /// The position of the drawable in the layout. public float GetLayoutPosition(Drawable drawable) { - if (!layoutChildren.ContainsKey(drawable)) + if (!layoutChildren.TryGetValue(drawable, out float value)) throw new InvalidOperationException($"Cannot get layout position of drawable which is not contained within this {nameof(FlowContainer)}."); - return layoutChildren[drawable]; + return value; } protected override bool UpdateChildrenLife() diff --git a/osu.Framework/Graphics/Containers/SearchContainer.cs b/osu.Framework/Graphics/Containers/SearchContainer.cs index e9de9e7e4b..47edd3dd61 100644 --- a/osu.Framework/Graphics/Containers/SearchContainer.cs +++ b/osu.Framework/Graphics/Containers/SearchContainer.cs @@ -121,7 +121,7 @@ protected void Filter() private void performFilter() { string[] terms = (searchTerm ?? string.Empty).Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - matchSubTree(this, terms, terms.Any(), allowNonContiguousMatching); + matchSubTree(this, terms, terms.Length > 0, allowNonContiguousMatching); } private bool matchSubTree(Drawable drawable, IReadOnlyList searchTerms, bool searchActive, bool nonContiguousMatching) diff --git a/osu.Framework/Graphics/OpenGL/Buffers/GLShaderStorageBufferObject.cs b/osu.Framework/Graphics/OpenGL/Buffers/GLShaderStorageBufferObject.cs index ee4e3d88c9..4ab867e34f 100644 --- a/osu.Framework/Graphics/OpenGL/Buffers/GLShaderStorageBufferObject.cs +++ b/osu.Framework/Graphics/OpenGL/Buffers/GLShaderStorageBufferObject.cs @@ -18,7 +18,7 @@ internal class GLShaderStorageBufferObject : IShaderStorageBufferObject 0) { var currentPreselected = PreselectedItem; int targetPreselectionIndex = visibleMenuItemsList.IndexOf(currentPreselected); diff --git a/osu.Framework/Graphics/UserInterface/TabControl.cs b/osu.Framework/Graphics/UserInterface/TabControl.cs index e4cc0dad5c..1ed586dd38 100644 --- a/osu.Framework/Graphics/UserInterface/TabControl.cs +++ b/osu.Framework/Graphics/UserInterface/TabControl.cs @@ -203,8 +203,8 @@ protected override void Update() protected override void LoadComplete() { // Default to first selection in list, if we can - if (firstSelection && SelectFirstTabByDefault && !Current.Disabled && Items.Any()) - Current.Value = Items.First(); + if (firstSelection && SelectFirstTabByDefault && !Current.Disabled && Items.Count > 0) + Current.Value = Items[0]; Current.BindValueChanged(v => { @@ -545,7 +545,7 @@ protected override IEnumerable ComputeLayoutPositions() private void updateChildIfNeeded(TabItem child, bool isVisible) { - if (!tabVisibility.ContainsKey(child) || tabVisibility[child] != isVisible) + if (!tabVisibility.TryGetValue(child, out bool currentVisibility) || currentVisibility != isVisible) { TabVisibilityChanged?.Invoke(child, isVisible); tabVisibility[child] = isVisible; diff --git a/osu.Framework/Graphics/Visualisation/PropertyDisplay.cs b/osu.Framework/Graphics/Visualisation/PropertyDisplay.cs index 5088de48ae..02eb82e468 100644 --- a/osu.Framework/Graphics/Visualisation/PropertyDisplay.cs +++ b/osu.Framework/Graphics/Visualisation/PropertyDisplay.cs @@ -80,7 +80,7 @@ private void updateProperties(IDrawable source) foreach (var type in source.GetType().EnumerateBaseTypes()) { type.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) - .Where(m => m is FieldInfo || (m is PropertyInfo pi && pi.GetMethod != null && !pi.GetIndexParameters().Any())) + .Where(m => m is FieldInfo || (m is PropertyInfo pi && pi.GetMethod != null && pi.GetIndexParameters().Length == 0)) .ForEach(m => allMembers.Add(m)); } diff --git a/osu.Framework/Input/Bindings/KeyBindingContainer.cs b/osu.Framework/Input/Bindings/KeyBindingContainer.cs index 5f0149ded2..8ef1e9bb00 100644 --- a/osu.Framework/Input/Bindings/KeyBindingContainer.cs +++ b/osu.Framework/Input/Bindings/KeyBindingContainer.cs @@ -409,7 +409,7 @@ private List getInputQueue(IKeyBinding binding, bool rebuildIfEmpty = var currentQueue = keyBindingQueues[binding]; - if (rebuildIfEmpty && !currentQueue.Any()) + if (rebuildIfEmpty && currentQueue.Count == 0) currentQueue.AddRange(KeyBindingInputQueue); return currentQueue; diff --git a/osu.Framework/Input/Bindings/KeyCombination.cs b/osu.Framework/Input/Bindings/KeyCombination.cs index f2024456f9..4f30fc504d 100644 --- a/osu.Framework/Input/Bindings/KeyCombination.cs +++ b/osu.Framework/Input/Bindings/KeyCombination.cs @@ -32,7 +32,7 @@ namespace osu.Framework.Input.Bindings /// The keys. public KeyCombination(ICollection? keys) { - if (keys == null || !keys.Any()) + if (keys == null || keys.Count == 0) { Keys = none; return; diff --git a/osu.Framework/Input/Handlers/Midi/MidiHandler.cs b/osu.Framework/Input/Handlers/Midi/MidiHandler.cs index c414f98e06..41f8cc8d4d 100644 --- a/osu.Framework/Input/Handlers/Midi/MidiHandler.cs +++ b/osu.Framework/Input/Handlers/Midi/MidiHandler.cs @@ -182,10 +182,9 @@ private void readEvent(byte[] data, string senderId, ref int i, out byte eventTy // need running status to be interpreted correctly if (statusType <= 0x7F) { - if (!runningStatus.ContainsKey(senderId)) + if (!runningStatus.TryGetValue(senderId, out eventType)) throw new InvalidDataException($"Received running status of sender {senderId}, but no event type was stored"); - eventType = runningStatus[senderId]; key = statusType; velocity = data[i++]; return; diff --git a/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs b/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs index 2d941ec173..cf568ee19d 100644 --- a/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs +++ b/osu.Framework/Platform/Windows/WindowsMouseHandler_SDL2.cs @@ -65,7 +65,9 @@ private unsafe IntPtr onWndProcSDL2(IntPtr userData, IntPtr hWnd, uint message, int payloadSize = sizeof(RawInputData); +#pragma warning disable CA2020 // Prevent behavioral change for IntPtr conversion Native.Input.GetRawInputData((IntPtr)lParam, RawInputCommand.Input, out var data, ref payloadSize, sizeof(RawInputHeader)); +#pragma warning restore CA2020 if (data.Header.Type != RawInputType.Mouse) return IntPtr.Zero; diff --git a/osu.Framework/Testing/TestBrowser.cs b/osu.Framework/Testing/TestBrowser.cs index 8f5b708471..5f80650af4 100644 --- a/osu.Framework/Testing/TestBrowser.cs +++ b/osu.Framework/Testing/TestBrowser.cs @@ -403,7 +403,7 @@ private void finishLoad(TestScene newTest, Action onCompletion) var methods = newTest.GetType().GetMethods(); var soloTests = methods.Where(m => m.GetCustomAttribute(typeof(SoloAttribute), false) != null).ToArray(); - if (soloTests.Any()) + if (soloTests.Length > 0) methods = soloTests; foreach (var m in methods) @@ -519,7 +519,7 @@ void addSetUpSteps() { var setUpMethods = ReflectionUtils.GetMethodsWithAttribute(newTest.GetType(), typeof(SetUpAttribute), true); - if (setUpMethods.Any()) + if (setUpMethods.Length > 0) { CurrentTest.AddStep(new SingleStepButton {