diff --git a/c#/Directory.Build.props b/c#/Directory.Build.props index 24df1573..c684f1c8 100644 --- a/c#/Directory.Build.props +++ b/c#/Directory.Build.props @@ -10,6 +10,7 @@ true Recommended true + true diff --git a/c#/GlobalSuppressions.cs b/c#/GlobalSuppressions.cs index 46ca515e..b3c9ec1a 100644 --- a/c#/GlobalSuppressions.cs +++ b/c#/GlobalSuppressions.cs @@ -1,59 +1,52 @@ // ReSharper disable once RedundantUsingDirective using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Major Code Smell", "S125:Sections of code should not be commented out")] -[assembly: SuppressMessage("Minor Code Smell", "S3604:Member initializer values should not be redundant")] +[assembly: SuppressMessage("Class Design", "AV1008:Class should not be static")] +[assembly: SuppressMessage("Class Design", "AV1010:Member hides inherited member")] +[assembly: SuppressMessage("Correctness", "SS019:Switch should have default label.")] [assembly: SuppressMessage("Critical Bug", "S6674:Log message template should be syntactically correct")] -[assembly: SuppressMessage("Roslynator", "RCS1001:Add braces (when expression spans over multiple lines).")] -[assembly: SuppressMessage("Roslynator", "RCS1139:Add summary element to documentation comment.")] -[assembly: SuppressMessage("Roslynator", "RCS1156:Use string.Length instead of comparison with empty string.")] -[assembly: SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods")] -[assembly: SuppressMessage("Style", "CC0061:Asynchronous method can be terminated with the 'Async' keyword.")] -[assembly: SuppressMessage("Naming", "AV1755:Name of async method should end with Async or TaskAsync")] -[assembly: SuppressMessage("Naming", "AV1706:Identifier contains an abbreviation or is too short")] -[assembly: SuppressMessage("Maintainability", "AV1580:Method argument calls a nested method")] -[assembly: SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements")] -[assembly: SuppressMessage("Miscellaneous Design", "AV1210:Catch a specific exception instead of Exception, SystemException or ApplicationException")] +[assembly: SuppressMessage("Design", "CC0021:Use nameof")] [assembly: SuppressMessage("Design", "CC0031:Check for null before calling a delegate")] -[assembly: SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] -[assembly: SuppressMessage("Design", "MA0051:Method is too long")] -[assembly: SuppressMessage("Usage", "MA0004:Use Task.ConfigureAwait")] +[assembly: SuppressMessage("Design", "CC0091:Use static method", Justification = "https://github.com/code-cracker/code-cracker/issues/1087")] [assembly: SuppressMessage("Design", "CC0120:Your Switch maybe include default clause")] -[assembly: SuppressMessage("Correctness", "SS019:Switch should have default label.")] -[assembly: SuppressMessage("Maintainability", "AV1555:Avoid using non-(nullable-)boolean named arguments")] -[assembly: SuppressMessage("Maintainability", "AV1535:Missing block in case or default clause of switch statement")] -[assembly: SuppressMessage("Maintainability", "CC0097:You have missing/unexistent parameters in Xml Docs")] [assembly: SuppressMessage("Design", "MA0048:File name must match type name")] +[assembly: SuppressMessage("Design", "MA0051:Method is too long")] +[assembly: SuppressMessage("Documentation", "AV2305:Missing XML comment for internally visible type, member or parameter")] +[assembly: SuppressMessage("Framework", "AV2220:Simple query should be replaced by extension method call")] +[assembly: SuppressMessage("Maintainability", "AV1500:Member or local function contains too many statements")] [assembly: SuppressMessage("Maintainability", "AV1507:File contains multiple types")] -[assembly: SuppressMessage("Style", "MA0007:Add a comma after the last value")] -[assembly: SuppressMessage("Style", "MA0003:Add parameter name to improve readability")] -[assembly: SuppressMessage("Design", "CC0021:Use nameof")] -[assembly: SuppressMessage("Naming", "AV1704:Identifier contains one or more digits in its name")] -[assembly: SuppressMessage("Usage", "MA0006:Use String.Equals instead of equality operator")] +[assembly: SuppressMessage("Maintainability", "AV1532:Loop statement contains nested loop")] +[assembly: SuppressMessage("Maintainability", "AV1535:Missing block in case or default clause of switch statement")] [assembly: SuppressMessage("Maintainability", "AV1537:If-else-if construct should end with an unconditional else clause")] [assembly: SuppressMessage("Maintainability", "AV1554:Method contains optional parameter in type hierarchy")] -[assembly: SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] -[assembly: SuppressMessage("Class Design", "AV1010:Member hides inherited member")] +[assembly: SuppressMessage("Maintainability", "AV1555:Avoid using non-(nullable-)boolean named arguments")] +[assembly: SuppressMessage("Maintainability", "AV1561:Signature contains too many parameters")] [assembly: SuppressMessage("Maintainability", "AV1562:Do not declare a parameter as ref or out")] -[assembly: SuppressMessage("Maintainability", "AV1532:Loop statement contains nested loop")] -[assembly: SuppressMessage("Design", "CC0091:Use static method", Justification = "https://github.com/code-cracker/code-cracker/issues/1087")] -[assembly: SuppressMessage("Usage", "MA0015:Specify the parameter name in ArgumentException")] -[assembly: SuppressMessage("Class Design", "AV1008:Class should not be static")] -[assembly: SuppressMessage("Framework", "AV2220:Simple query should be replaced by extension method call")] +[assembly: SuppressMessage("Maintainability", "AV1564:Parameter in public or internal member is of type bool or bool?")] +[assembly: SuppressMessage("Maintainability", "AV1580:Method argument calls a nested method")] +[assembly: SuppressMessage("Maintainability", "CC0097:You have missing/unexistent parameters in Xml Docs")] +[assembly: SuppressMessage("Major Code Smell", "S125:Sections of code should not be commented out")] +[assembly: SuppressMessage("Minor Code Smell", "S3604:Member initializer values should not be redundant")] +[assembly: SuppressMessage("Miscellaneous Design", "AV1210:Catch a specific exception instead of Exception, SystemException or ApplicationException")] +[assembly: SuppressMessage("Naming", "AV1704:Identifier contains one or more digits in its name")] +[assembly: SuppressMessage("Naming", "AV1706:Identifier contains an abbreviation or is too short")] +[assembly: SuppressMessage("Naming", "AV1755:Name of async method should end with Async or TaskAsync")] +[assembly: SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")] +[assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords")] +[assembly: SuppressMessage("Performance", "CA1848:Use the LoggerMessage delegates")] +[assembly: SuppressMessage("Roslynator", "RCS1001:Add braces (when expression spans over multiple lines).")] +[assembly: SuppressMessage("Roslynator", "RCS1139:Add summary element to documentation comment.")] +[assembly: SuppressMessage("Roslynator", "RCS1156:Use string.Length instead of comparison with empty string.")] [assembly: SuppressMessage("Style", "CC0001:You should use 'var' whenever possible.")] [assembly: SuppressMessage("Style", "CC0037:Remove commented code.")] -[assembly: SuppressMessage("Documentation", "AV2305:Missing XML comment for internally visible type, member or parameter")] [assembly: SuppressMessage("Style", "CC0061:Asynchronous method can be terminated with the 'Async' keyword.")] [assembly: SuppressMessage("Style", "MA0003:Add parameter name to improve readability")] [assembly: SuppressMessage("Style", "MA0007:Add a comma after the last value")] [assembly: SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods")] +[assembly: SuppressMessage("Usage", "CC0057:Unused parameters")] [assembly: SuppressMessage("Usage", "MA0004:Use Task.ConfigureAwait")] [assembly: SuppressMessage("Usage", "MA0006:Use String.Equals instead of equality operator")] [assembly: SuppressMessage("Usage", "MA0015:Specify the parameter name in ArgumentException")] -[assembly: SuppressMessage("Performance", "CA1848:Use the LoggerMessage delegates")] -[assembly: SuppressMessage("Usage", "CC0057:Unused parameters")] -[assembly: SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")] -[assembly: SuppressMessage("Naming", "CA1716:Identifiers should not match keywords")] [assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented")] diff --git a/c#/crawler/src/GlobalUsings.cs b/c#/crawler/src/GlobalUsings.cs index 14793a8a..5b8250c6 100644 --- a/c#/crawler/src/GlobalUsings.cs +++ b/c#/crawler/src/GlobalUsings.cs @@ -7,6 +7,7 @@ global using System.Data; global using System.Diagnostics; global using System.Diagnostics.CodeAnalysis; +global using System.Globalization; global using System.Linq.Expressions; global using System.Reflection; global using System.Text.Json; diff --git a/c#/crawler/src/SonicPusher.cs b/c#/crawler/src/SonicPusher.cs index a8010339..1f014a9f 100644 --- a/c#/crawler/src/SonicPusher.cs +++ b/c#/crawler/src/SonicPusher.cs @@ -42,7 +42,8 @@ public float PushPost(Fid fid, string type, PostId id, RepeatedField? c try { foreach (var text in contentTexts.Chunk(30000)) // https://github.com/spikensbror-dotnet/nsonic/issues/11 - Ingest.Push($"{CollectionPrefix}{type}_content", $"f{fid}", id.ToString(), text.ToString(), "cmn"); + Ingest.Push($"{CollectionPrefix}{type}_content", $"f{fid}", + id.ToString(CultureInfo.InvariantCulture), text.ToString(), "cmn"); } catch (Exception e) { diff --git a/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs b/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs index 1641dc86..6ef1017b 100644 --- a/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs +++ b/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs @@ -104,7 +104,8 @@ protected override void LogTrace() lock (_crawling) lock (_failed) { - logger.LogTrace("Lock: type={} crawlingIdCount={} crawlingPageCount={} crawlingPageCountsKeyById={} failedIdCount={} failedPageCount={} failures={}", LockType, + logger.LogTrace("Lock: type={} crawlingIdCount={} crawlingPageCount={} crawlingPageCountsKeyById={}" + + " failedIdCount={} failedPageCount={} failures={}", LockType, _crawling.Count, _crawling.Values.Sum(d => d.Count), Helper.UnescapedJsonSerialize(_crawling.ToDictionary(pair => pair.Key.ToString(), pair => pair.Value.Count)), _failed.Count, _failed.Values.Sum(d => d.Count), diff --git a/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs b/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs index 7ba534e9..b6605371 100644 --- a/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs +++ b/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs @@ -41,7 +41,7 @@ private async Task CrawlThread var json = await requester.RequestJson( $"{ClientRequester.LegacyClientApiDomain}/c/f/pb/page", "8.8.8.8", new() { - {"kz", tid.ToString()}, + {"kz", tid.ToString(CultureInfo.InvariantCulture)}, {"pn", "1"}, // rn have to be at least 2 @@ -60,7 +60,7 @@ private async Task CrawlThread }, JsonValueKind.String => () => { // https://stackoverflow.com/questions/62100000/why-doesnt-system-text-json-jsonelement-have-trygetstring-or-trygetboolean/62100246#62100246 - var r = int.TryParse(errorCodeProp.GetString(), out var p); + var r = int.TryParse(errorCodeProp.GetString(), CultureInfo.InvariantCulture, out var p); return (p, r); }, _ => () => (0, false) @@ -85,7 +85,7 @@ private async Task CrawlThread ? threadInfo.TryGetProperty("phone_type", out var phoneType) ? new ThreadPost { - Tid = Tid.Parse(thread.GetStrProp("id")), + Tid = Tid.Parse(thread.GetStrProp("id"), CultureInfo.InvariantCulture), AuthorPhoneType = phoneType.GetString().NullIfEmpty() } : throw new TiebaException(shouldRetry: false, diff --git a/c#/crawler/src/Tieba/Crawl/UserParserAndSaver.cs b/c#/crawler/src/Tieba/Crawl/UserParserAndSaver.cs index aa32ed3e..8b42908a 100644 --- a/c#/crawler/src/Tieba/Crawl/UserParserAndSaver.cs +++ b/c#/crawler/src/Tieba/Crawl/UserParserAndSaver.cs @@ -45,7 +45,7 @@ public void ParseUsers(IEnumerable users) => { static (string Portrait, uint? UpdateTime) ExtractPortrait(string portrait) => ExtractPortraitRegex().Match(portrait) is {Success: true} m - ? (m.Groups["portrait"].Value, Time.Parse(m.Groups["timestamp"].ValueSpan)) + ? (m.Groups["portrait"].Value, Time.Parse(m.Groups["timestamp"].ValueSpan, CultureInfo.InvariantCulture)) : (portrait, null); var uid = el.Uid; diff --git a/c#/crawler/src/Worker/ArchiveCrawlWorker.cs b/c#/crawler/src/Worker/ArchiveCrawlWorker.cs index 7ae16a60..38eea613 100644 --- a/c#/crawler/src/Worker/ArchiveCrawlWorker.cs +++ b/c#/crawler/src/Worker/ArchiveCrawlWorker.cs @@ -29,11 +29,12 @@ public class ArchiveCrawlWorker( public static float GetCumulativeAverage(float currentCa, float previousCa, int currentIndex) => (currentCa + ((currentIndex - 1) * previousCa)) / currentIndex; + [SuppressMessage("Correctness", "SS002:DateTime.Now was referenced")] public static (string Relative, string At) GetEta(int total, int completed, float averageDurationInMs) { var etaTimeSpan = TimeSpan.FromMilliseconds((total - completed) * averageDurationInMs); return (etaTimeSpan.Humanize(precision: 5, minUnit: TimeUnit.Second), - DateTime.Now.Add(etaTimeSpan).ToString("MM-dd HH:mm:ss")); + DateTime.Now.Add(etaTimeSpan).ToString("MM-dd HH:mm:ss", CultureInfo.CurrentCulture)); } protected override async Task DoWork(CancellationToken stoppingToken) diff --git a/c#/crawler/src/Worker/ForumModeratorRevisionCrawlWorker.cs b/c#/crawler/src/Worker/ForumModeratorRevisionCrawlWorker.cs index b32ed993..dbf32ee6 100644 --- a/c#/crawler/src/Worker/ForumModeratorRevisionCrawlWorker.cs +++ b/c#/crawler/src/Worker/ForumModeratorRevisionCrawlWorker.cs @@ -40,7 +40,7 @@ where e.IsCrawling { var type = typeEl.QuerySelector("div.title")?.Children .Select(el => el.ClassList) - .First(classNames => classNames.Any(className => className.EndsWith("_icon"))) + .First(classNames => classNames.Any(className => className.EndsWith("_icon", StringComparison.Ordinal))) .Select(className => className.Split("_")[0]) .First(className => !string.IsNullOrWhiteSpace(className)); if (string.IsNullOrEmpty(type)) throw new TiebaException(); diff --git a/c#/imagePipeline/src/Consumer/MetadataConsumer.cs b/c#/imagePipeline/src/Consumer/MetadataConsumer.cs index f0964942..a4bc6867 100644 --- a/c#/imagePipeline/src/Consumer/MetadataConsumer.cs +++ b/c#/imagePipeline/src/Consumer/MetadataConsumer.cs @@ -1,4 +1,3 @@ -using System.Globalization; using System.IO.Hashing; using System.Text.RegularExpressions; using NetTopologySuite.Geometries; @@ -101,7 +100,7 @@ private Func GetImageMetaData if (rawBytes == null || rawBytes.Length == 0) return null; if (rawBytes.Length > 65535) _logger.LogWarning("Embedded {} in image contains {} bytes", - typeof(TEmbeddedMetadata).Name.ToUpper(), rawBytes.Length); + typeof(TEmbeddedMetadata).Name.ToUpperInvariant(), rawBytes.Length); var xxHash3 = XxHash3.HashToUInt64(rawBytes); return new() { @@ -281,7 +280,7 @@ private static partial class ExifDateTimeTagValuesParser // https://stackoverflow.com/questions/4483886/how-can-i-get-a-count-of-the-total-number-of-digits-in-a-number/51099524#51099524 [SuppressMessage("Major Code Smell", "S3358:Ternary operators should not be nested")] static int CountDigits(int n) => n == 0 ? 1 : (n > 0 ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n)); - var hasFractionalSeconds = int.TryParse(exifFractionalSeconds, out var fractionalSeconds); + var hasFractionalSeconds = int.TryParse(exifFractionalSeconds, CultureInfo.InvariantCulture, out var fractionalSeconds); fractionalSeconds = hasFractionalSeconds ? fractionalSeconds : 0; if (exifDateTime == "00000" && fractionalSeconds == 0) return null; @@ -290,7 +289,8 @@ private static partial class ExifDateTimeTagValuesParser ?? ParseWithOverflowedTimeParts(exifDateTime) ?? ParseAsUnixTimestamp(exifDateTime) ?? throw new ArgumentException( - $"Failed to parse provided EXIF date time \"{exifDateTime}\" with fractional seconds {fractionalSeconds}."); + $"Failed to parse provided EXIF date time \"{exifDateTime}\"" + + $" with fractional seconds {fractionalSeconds.ToString(CultureInfo.InvariantCulture)}."); return fractionalSeconds == 0 ? ret : ret with { DateTime = ret.DateTime.AddSeconds(fractionalSeconds / Math.Pow(10, CountDigits(fractionalSeconds))) @@ -333,7 +333,7 @@ private static partial class ExifDateTimeTagValuesParser : default; return dateTimeOffset == default ? null - : new(dateTimeOffset.DateTime, dateTimeOffset.ToString("zzz")); + : new(dateTimeOffset.DateTime, dateTimeOffset.ToString("zzz", CultureInfo.InvariantCulture)); } [GeneratedRegex( @@ -349,13 +349,14 @@ private static partial class ExifDateTimeTagValuesParser ExtractExifDateTimePartsRegex().Match(exifDateTime) is not {Success: true} m ? null : new(new DateTime( - int.Parse(m.Groups["year"].ValueSpan), - int.Parse(m.Groups["month"].ValueSpan), - int.Parse(m.Groups["day"].ValueSpan), + int.Parse(m.Groups["year"].ValueSpan, CultureInfo.InvariantCulture), + int.Parse(m.Groups["month"].ValueSpan, CultureInfo.InvariantCulture), + int.Parse(m.Groups["day"].ValueSpan, CultureInfo.InvariantCulture), hour: 0, minute: 0, second: 0, DateTimeKind.Unspecified) - .AddHours(int.Parse(m.Groups["hour"].ValueSpan)) - .AddMinutes(int.Parse(m.Groups["minute"].ValueSpan)) - .AddSeconds(int.Parse(m.Groups["second"].ValueSpan)), Offset: null); + .AddHours(int.Parse(m.Groups["hour"].ValueSpan, CultureInfo.InvariantCulture)) + .AddMinutes(int.Parse(m.Groups["minute"].ValueSpan, CultureInfo.InvariantCulture)) + .AddSeconds(int.Parse(m.Groups["second"].ValueSpan, CultureInfo.InvariantCulture)), + Offset: null); private static DateTimeAndOffset? ParseAsUnixTimestamp(string exifDateTime) => long.TryParse(exifDateTime, NumberStyles.Integer, diff --git a/c#/imagePipeline/src/Consumer/OcrConsumer.cs b/c#/imagePipeline/src/Consumer/OcrConsumer.cs index 3d73f27c..73fa8ea3 100644 --- a/c#/imagePipeline/src/Consumer/OcrConsumer.cs +++ b/c#/imagePipeline/src/Consumer/OcrConsumer.cs @@ -51,7 +51,7 @@ protected override IEnumerable ConsumeInternal( RotationDegrees = result.TextBox.Angle.RoundToUshort(), Recognizer = result switch { - PaddleOcrRecognitionResult r => "PaddleOCR" + Enum.GetName(r.ModelVersion)?.ToLower(), + PaddleOcrRecognitionResult r => "PaddleOCR" + Enum.GetName(r.ModelVersion)?.ToLowerInvariant(), TesseractRecognitionResult {IsVertical: false} => "TesseractHorizontal", TesseractRecognitionResult {IsVertical: true} => "TesseractVertical", _ => throw new ArgumentOutOfRangeException(nameof(result), result, null) diff --git a/c#/imagePipeline/src/GlobalUsings.cs b/c#/imagePipeline/src/GlobalUsings.cs index a3b7c357..2afe376b 100644 --- a/c#/imagePipeline/src/GlobalUsings.cs +++ b/c#/imagePipeline/src/GlobalUsings.cs @@ -1,6 +1,7 @@ #pragma warning disable SA1210 // Using directives should be ordered alphabetically by namespace global using System.ComponentModel.DataAnnotations; global using System.Diagnostics.CodeAnalysis; +global using System.Globalization; global using System.Linq.Expressions; global using System.Text.Json; global using System.Threading.Channels; @@ -9,12 +10,12 @@ global using Autofac; global using Autofac.Features.OwnedInstances; global using CommunityToolkit.Diagnostics; +global using LanguageExt; global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using OpenCvSharp; -global using LanguageExt; global using Polly; global using Polly.Extensions.Http; global using Polly.Registry;