diff --git a/c#/.editorconfig b/c#/.editorconfig index a2329a98..fd644e34 100644 --- a/c#/.editorconfig +++ b/c#/.editorconfig @@ -258,6 +258,7 @@ csharp_prefer_simple_using_statement = true:suggestion csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion ############################### # VB Coding Conventions # ############################### diff --git a/c#/GlobalSuppressions.cs b/c#/GlobalSuppressions.cs index bf7d7c41..93b44952 100644 --- a/c#/GlobalSuppressions.cs +++ b/c#/GlobalSuppressions.cs @@ -1,3 +1,4 @@ +// ReSharper disable once RedundantUsingDirective using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Major Code Smell", "S125:Sections of code should not be commented out")] @@ -6,6 +7,7 @@ [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("Critical Bug", "S6674:Log message template should be syntactically correct")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1601:Partial elements should be documented")] diff --git a/c#/crawler/src/Tieba/ClientRequester.cs b/c#/crawler/src/Tieba/ClientRequester.cs index a7889fee..cd4483a7 100644 --- a/c#/crawler/src/Tieba/ClientRequester.cs +++ b/c#/crawler/src/Tieba/ClientRequester.cs @@ -49,8 +49,7 @@ await Request(() => PostProtoBuf(url, clientVersion, requestParam, commonParamSe _ = stream.Seek(0, SeekOrigin.Begin); using var stream2 = new MemoryStream((int)stream.Length); stream.CopyTo(stream2); - if (!stream2.TryGetBuffer(out var buffer)) - throw new ObjectDisposedException(nameof(stream2)); + ObjectDisposedException.ThrowIf(!stream2.TryGetBuffer(out var buffer), stream2); var responseBody = Encoding.UTF8.GetString(buffer); // the invalid protoBuf bytes usually is just a plain html string diff --git a/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs b/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs index b8b3431f..1641dc86 100644 --- a/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs +++ b/c#/crawler/src/Tieba/Crawl/CrawlerLocks.cs @@ -63,6 +63,7 @@ public void ReleaseRange(LockId lockId, IEnumerable pages) public void AcquireFailed(LockId lockId, Page page, FailureCount failureCount) { + // ReSharper disable once InconsistentlySynchronizedField var maxRetry = _config.GetValue("MaxRetryTimes", 5); if (failureCount >= maxRetry) { diff --git a/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs b/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs index 0ec8a17e..5ac02deb 100644 --- a/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs +++ b/c#/crawler/src/Tieba/Crawl/ThreadLateCrawlerAndSaver.cs @@ -35,9 +35,7 @@ private async Task CrawlThread (Tid tid, FailureCount failureCount, CancellationToken stoppingToken = default) { var crawlerLockId = new CrawlerLocks.LockId(fid, tid); -#pragma warning disable SA1003 // Symbols should be spaced correctly - if (_locks.AcquireRange(crawlerLockId, [(Page)1]).Count == 0) return null; -#pragma warning restore SA1003 // Symbols should be spaced correctly + if (_locks.AcquireRange(crawlerLockId, [1]).Count == 0) return null; try { var json = await requester.RequestJson( diff --git a/c#/crawler/src/Worker/ResumeSuspendPostContentsPushingWorker.cs b/c#/crawler/src/Worker/ResumeSuspendPostContentsPushingWorker.cs index 13a7069b..7e36fd00 100644 --- a/c#/crawler/src/Worker/ResumeSuspendPostContentsPushingWorker.cs +++ b/c#/crawler/src/Worker/ResumeSuspendPostContentsPushingWorker.cs @@ -8,6 +8,7 @@ public class ResumeSuspendPostContentsPushingWorker( public static string GetFilePath(string postType) => Path.Combine(AppContext.BaseDirectory, $"suspendPostContentsPushIntoSonic.{postType}.csv"); + [SuppressMessage("Reliability", "CA2021:Do not call Enumerable.Cast or Enumerable.OfType with incompatible types", Justification = "https://github.com/dotnet/roslyn-analyzers/issues/7031")] protected override Task DoWork(CancellationToken stoppingToken) { foreach (var postType in new[] {"replies", "subReplies"}) diff --git a/c#/crawler/tbm.Crawler.csproj b/c#/crawler/tbm.Crawler.csproj index 012b2bde..df62dbb5 100644 --- a/c#/crawler/tbm.Crawler.csproj +++ b/c#/crawler/tbm.Crawler.csproj @@ -19,17 +19,9 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + + + diff --git a/c#/imagePipeline/src/ImageBatchConsumingWorker.cs b/c#/imagePipeline/src/ImageBatchConsumingWorker.cs index 4cc9782a..d58e2ffd 100644 --- a/c#/imagePipeline/src/ImageBatchConsumingWorker.cs +++ b/c#/imagePipeline/src/ImageBatchConsumingWorker.cs @@ -156,8 +156,7 @@ ImageKeyWithMatrix DecodeFrame(ImageFrame frame, int frameIndex) using var frameImage = Image.LoadPixelData(frameBytes, frame.Width, frame.Height); using var stream = new MemoryStream(); frameImage.SaveAsPng(stream); - if (!stream.TryGetBuffer(out var buffer)) - throw new ObjectDisposedException(nameof(stream)); + ObjectDisposedException.ThrowIf(!stream.TryGetBuffer(out var buffer), stream); #pragma warning disable IDISP001 // Dispose created var frameMat = Cv2.ImDecode(buffer, ImreadModes.Unchanged); #pragma warning restore IDISP001 // Dispose created diff --git a/c#/imagePipeline/src/Ocr/PaddleOcrRecognizerAndDetector.cs b/c#/imagePipeline/src/Ocr/PaddleOcrRecognizerAndDetector.cs index aab08a8e..6fac7417 100644 --- a/c#/imagePipeline/src/Ocr/PaddleOcrRecognizerAndDetector.cs +++ b/c#/imagePipeline/src/Ocr/PaddleOcrRecognizerAndDetector.cs @@ -23,8 +23,8 @@ public PaddleOcrRecognizerAndDetector(IConfiguration config, string script) public void Dispose() => _ocr?.Dispose(); [SuppressMessage("Major Code Smell", "S3928:Parameter names used into ArgumentException constructors should match an existing one ", Justification = "https://github.com/SonarSource/sonar-dotnet/issues/8386#issuecomment-1847872210")] - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1119:Statement should not use unnecessary parenthesis", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3730")] [SuppressMessage("Usage", "CA2208:Instantiate argument exceptions correctly")] + [SuppressMessage("ReSharper", "StringLiteralTypo")] public async Task Initialize(CancellationToken stoppingToken = default) => _ocr ??= await (_script switch { // https://en.wikipedia.org/wiki/Template:ISO_15924_script_codes_and_related_Unicode_data diff --git a/c#/imagePipeline/src/Ocr/TesseractRecognizer.cs b/c#/imagePipeline/src/Ocr/TesseractRecognizer.cs index e417fc40..28b4c400 100644 --- a/c#/imagePipeline/src/Ocr/TesseractRecognizer.cs +++ b/c#/imagePipeline/src/Ocr/TesseractRecognizer.cs @@ -10,6 +10,7 @@ public sealed partial class TesseractRecognizer(IConfiguration config, string sc public delegate TesseractRecognizer New(string script); + [SuppressMessage("ReSharper", "StringLiteralTypo")] private Lazy TesseractInstanceHorizontal => _tesseractInstanceHorizontal ??= new(script switch { // https://en.wikipedia.org/wiki/Template:ISO_15924_script_codes_and_related_Unicode_data "Hans" => CreateTesseract("best/chi_sim+best/eng"), @@ -19,6 +20,7 @@ public sealed partial class TesseractRecognizer(IConfiguration config, string sc _ => throw new ArgumentOutOfRangeException(nameof(script), script, "Unsupported script.") }); + [SuppressMessage("ReSharper", "StringLiteralTypo")] private Lazy TesseractInstanceVertical => _tesseractInstanceVertical ??= new(script switch { "Hans" => CreateTesseract("best/chi_sim_vert", isVertical: true), @@ -53,6 +55,8 @@ public Func RecognizePreprocess var (imageKey, box, preprocessedTextBoxMat) = textBox; using var mat = preprocessedTextBoxMat; var isVertical = (float)mat.Width / mat.Height < AspectRatioThresholdToConsiderAsVertical; + + // ReSharper disable once StringLiteralTypo if (isVertical && script == "Latn") isVertical = false; // there's no vertical latin var tesseract = isVertical ? TesseractInstanceVertical : TesseractInstanceHorizontal; tesseract.Value.Run(mat, out _, out var rects, out var texts, out var confidences); @@ -95,7 +99,8 @@ public static IEnumerable PreprocessTextBoxes( // http://www.fmwconcepts.com/imagemagick/threshold_comparison/index.php _ = Cv2.Threshold(mat, mat, thresh: 0, maxval: 255, ThresholdTypes.Otsu | ThresholdTypes.Binary); - if (degrees != 0) RotateMatrix(mat, degrees); + // https://stackoverflow.com/questions/9392869/where-do-i-find-the-machine-epsilon-in-c + if (MathF.Abs(degrees) < MathF.Pow(2, -24)) RotateMatrix(mat, degrees); // https://github.com/tesseract-ocr/tesseract/issues/427 Cv2.CopyMakeBorder(mat, mat, 10, 10, 10, 10, BorderTypes.Constant, new(0, 0, 0)); @@ -114,8 +119,9 @@ private static float GetRotationDegrees(RotatedRect rotatedRect) var xAxisDiff = bottomLeft.X - topLeft.X; var yAxisDiff = bottomLeft.Y - topLeft.Y; + // atan2(y,x) is the radians of the angle C in a right triangle with side b=4 (xAxisDiff) and side c=1 (yAxisDiff) // https://www.calculator.net/triangle-calculator.html?vc=&vx=4&vy=&va=90&vz=1&vb=&angleunits=d&x=53&y=29 - return (float)(Math.Atan2(xAxisDiff, yAxisDiff) * 180 / Math.PI); // radians to degrees + return (float)double.RadiansToDegrees(Math.Atan2(xAxisDiff, yAxisDiff)); } private static void RotateMatrix(Mat src, float degrees) @@ -126,7 +132,9 @@ private static void RotateMatrix(Mat src, float degrees) var boundingRect = new RotatedRect(default, new(src.Width, src.Height), degrees).BoundingRect(); rotationMat.Set(0, 2, rotationMat.Get(0, 2) + (boundingRect.Width / 2f) - (src.Width / 2f)); rotationMat.Set(1, 2, rotationMat.Get(1, 2) + (boundingRect.Height / 2f) - (src.Height / 2f)); - Cv2.WarpAffine(src, src, rotationMat, boundingRect.Size); + + // https://stackoverflow.com/questions/39371507/image-loses-quality-with-cv2-warpperspective + Cv2.WarpAffine(src, src, rotationMat, boundingRect.Size, InterpolationFlags.Nearest); } public record PreprocessedTextBox(ImageKey ImageKey, RotatedRect TextBox, Mat PreprocessedTextBoxMat); diff --git a/c#/imagePipeline/tbm.ImagePipeline.csproj b/c#/imagePipeline/tbm.ImagePipeline.csproj index 3ed8df71..9940351b 100644 --- a/c#/imagePipeline/tbm.ImagePipeline.csproj +++ b/c#/imagePipeline/tbm.ImagePipeline.csproj @@ -20,17 +20,9 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + + + diff --git a/c#/shared/src/ErrorableWorker.cs b/c#/shared/src/ErrorableWorker.cs index 74d66966..8f4d8855 100644 --- a/c#/shared/src/ErrorableWorker.cs +++ b/c#/shared/src/ErrorableWorker.cs @@ -26,8 +26,10 @@ protected async Task DoWorkWithExceptionLogging(CancellationToken stoppingToken) } catch (OperationCanceledException e) when (e.CancellationToken == stoppingToken) { +#pragma warning disable S6667 // Logging in a catch clause should pass the caught exception as a parameter. Logger.LogInformation("{}: {} CancellationToken={}", e.GetType().FullName, e.Message, e.CancellationToken); +#pragma warning restore S6667 // Logging in a catch clause should pass the caught exception as a parameter. } catch (Exception e) { diff --git a/c#/tbm.sln.DotSettings b/c#/tbm.sln.DotSettings new file mode 100644 index 00000000..151576a9 --- /dev/null +++ b/c#/tbm.sln.DotSettings @@ -0,0 +1,11 @@ + + False + True + True + True + + True + True + True + True +