From 00b5495471060ac287aaaa915534305208a6ef00 Mon Sep 17 00:00:00 2001 From: Marci Date: Thu, 27 Aug 2026 12:05:53 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=BC=95=E5=85=A5=20Meziantou.Analyze?= =?UTF-8?q?r=20+=20.editorconfig=20=E4=BB=A3=E7=A0=81=E8=A7=84=E8=8C=83?= =?UTF-8?q?=EF=BC=8C=E5=91=8A=E8=AD=A6=E6=B8=85=E9=9B=B6=E5=B9=B6=E5=90=AF?= =?UTF-8?q?=E7=94=A8=20TreatWarningsAsErrors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 52 +++++++++++++++++++ CHANGELOG.md | 1 + .../StreamFrame.Protocols.Xml.csproj | 10 +++- src/StreamFrame/DecodeErrorPolicy.cs | 19 +++++++ src/StreamFrame/FrameErrorEventArgs.cs | 12 ----- src/StreamFrame/FrameErrorKind.cs | 14 +++++ src/StreamFrame/StreamConnectionOptions.cs | 17 ------ src/StreamFrame/StreamFrame.csproj | 5 ++ .../StreamFrame.Tests.csproj | 4 ++ .../StreamingFrameEncodeTests.cs | 40 -------------- .../TestWrittenBufferWriter.cs | 43 +++++++++++++++ .../XmlDocumentCodecTests.cs | 4 +- 12 files changed, 149 insertions(+), 72 deletions(-) create mode 100644 .editorconfig create mode 100644 src/StreamFrame/DecodeErrorPolicy.cs create mode 100644 src/StreamFrame/FrameErrorKind.cs create mode 100644 test/StreamFrame.Tests/TestWrittenBufferWriter.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ffa30a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,52 @@ +root = true + +# 基础格式 +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.cs] +indent_style = space +indent_size = 4 + +[*.{csproj,slnx,yml,yaml,json,xml,props,targets}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +# --------------------------------------------------------------------------- +# .NET 分析器(SDK 内置 NetAnalyzers + Meziantou.Analyzer) +# --------------------------------------------------------------------------- +[*.{cs,vb}] + +# 安全类问题一律视为错误 +dotnet_analyzer_diagnostic.category-Security.severity = error + +# 逐条调优:与本项目刻意设计冲突的规则降为建议,其余保持默认(警告为主) +# 库代码大量"捕获一切异常并转为重连/隔离"是刻意设计(会话守护/回调隔离) +dotnet_diagnostic.CA1031.severity = suggestion + +# ILogger 扩展调用未走 LoggerMessage 源生成(性能优化建议,非正确性) +dotnet_diagnostic.CA1848.severity = suggestion +dotnet_diagnostic.MA0032.severity = none # Use TimeSpan.Zero\TimeSpan.FromMilliseconds 等风格建议 +dotnet_diagnostic.MA0051.severity = suggestion # 方法过长(StreamConnection 状态机刻意集中) + +# 事件签名 EventHandler 用非 EventArgs 载荷:.NET 4.5+ 支持且对用户更直接, +# 不引入 EventArgs 包装类型 +dotnet_diagnostic.MA0046.severity = none + +# 选项校验用 nameof(属性) 作为 ArgumentOutOfRangeException 参数名:校验对象是属性而非方法参数 +dotnet_diagnostic.MA0015.severity = none + +# 测试项目:xUnit 无同步上下文,ConfigureAwait 与比较器重载建议均为噪音 +[test/**.cs] +dotnet_diagnostic.MA0002.severity = none +dotnet_diagnostic.MA0004.severity = none +dotnet_diagnostic.MA0006.severity = none + +# demo 控制台程序无需 ConfigureAwait 语义 +[samples/**.cs] +dotnet_diagnostic.MA0004.severity = none diff --git a/CHANGELOG.md b/CHANGELOG.md index a39fbce..c74977c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - 明确 `Start(ct)` 的取消令牌语义(仅约束建立连接阶段):接口 XML 注释与 README 同步说明。 ### 新增 +- 代码质量棘轮:引入 Meziantou.Analyzer(SDK 自带 NetAnalyzers 之上的补充规则)与 `.editorconfig`,现存告警清零并把库项目的 `TreatWarningsAsErrors` 打开(告警即构建失败);`FrameErrorKind`/`DecodeErrorPolicy` 拆分为独立文件(一类型一文件)。 - `WaitForConnectedAsync`:等待连接进入 Connected(已连接立即完成;取消或 Dispose 以取消结束),替代轮询状态或 `Task.Delay` 盲等;demo 全部改用该 API。 - 可选 `ILogger` 构造参数:连接重试、会话故障、用户回调异常等内部事件输出到日志(此前 `Debug.WriteLine` 在 Release 构建中完全不可见,生产排障只能靠猜)。 - `ReceiveQueueCapacity` 选项:接收消息通道可设上限(默认 0 不限制);消费慢时解码暂停读取、TCP 背压自然传导到对端,封堵慢消费者导致的内存无限增长。 diff --git a/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj b/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj index c855cfa..984d4c8 100644 --- a/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj +++ b/src/StreamFrame.Protocols.Xml/StreamFrame.Protocols.Xml.csproj @@ -1,4 +1,4 @@ - + @@ -8,10 +8,18 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + net8.0 enable enable + true diff --git a/src/StreamFrame/DecodeErrorPolicy.cs b/src/StreamFrame/DecodeErrorPolicy.cs new file mode 100644 index 0000000..02fb900 --- /dev/null +++ b/src/StreamFrame/DecodeErrorPolicy.cs @@ -0,0 +1,19 @@ +namespace StreamFrame; + +/// +/// 帧结构完整但内容解码失败(codec 抛异常)时的处理策略。 +/// +public enum DecodeErrorPolicy +{ + /// + /// 断开连接并走自动重连(默认)。协议内容错乱后流状态通常不可信, + /// 重连是对端与本地同时回到一致状态的最短路径。 + /// + Disconnect, + + /// + /// 丢弃坏帧、继续解析后续帧。适合线路噪声多、流定界自身可靠(如长度前缀)的场景; + /// 坏帧会通过 FrameError 事件上报。 + /// + SkipFrame, +} diff --git a/src/StreamFrame/FrameErrorEventArgs.cs b/src/StreamFrame/FrameErrorEventArgs.cs index 968d49b..470f45e 100644 --- a/src/StreamFrame/FrameErrorEventArgs.cs +++ b/src/StreamFrame/FrameErrorEventArgs.cs @@ -1,17 +1,5 @@ namespace StreamFrame; -/// 帧层诊断事件的类别。 -public enum FrameErrorKind -{ - /// 帧结构完整,但帧内负载解码失败(codec 抛出异常)。 - DecodeFailed, - - /// 字节被帧定界器当作噪声/垃圾丢弃(如非法长度头重同步、STX/ETX 之外的杂散字节)。 - DiscardedByResync, - - /// 未完成帧的已缓冲字节超过上限,判定流不可恢复。 - IncompleteFrameOverflow, -} /// /// 事件的参数。 diff --git a/src/StreamFrame/FrameErrorKind.cs b/src/StreamFrame/FrameErrorKind.cs new file mode 100644 index 0000000..4a412e6 --- /dev/null +++ b/src/StreamFrame/FrameErrorKind.cs @@ -0,0 +1,14 @@ +namespace StreamFrame; + +/// 帧层诊断事件的类别。 +public enum FrameErrorKind +{ + /// 帧结构完整,但帧内负载解码失败(codec 抛出异常)。 + DecodeFailed, + + /// 字节被帧定界器当作噪声/垃圾丢弃(如非法长度头重同步、STX/ETX 之外的杂散字节)。 + DiscardedByResync, + + /// 未完成帧的已缓冲字节超过上限,判定流不可恢复。 + IncompleteFrameOverflow, +} diff --git a/src/StreamFrame/StreamConnectionOptions.cs b/src/StreamFrame/StreamConnectionOptions.cs index 876c0b2..162e94b 100644 --- a/src/StreamFrame/StreamConnectionOptions.cs +++ b/src/StreamFrame/StreamConnectionOptions.cs @@ -1,22 +1,5 @@ namespace StreamFrame; -/// -/// 帧结构完整但内容解码失败(codec 抛异常)时的处理策略。 -/// -public enum DecodeErrorPolicy -{ - /// - /// 断开连接并走自动重连(默认)。协议内容错乱后流状态通常不可信, - /// 重连是对端与本地同时回到一致状态的最短路径。 - /// - Disconnect, - - /// - /// 丢弃坏帧、继续解析后续帧。适合线路噪声多、流定界自身可靠(如长度前缀)的场景; - /// 坏帧会通过 FrameError 事件上报。 - /// - SkipFrame, -} /// /// 的可调参数。 diff --git a/src/StreamFrame/StreamFrame.csproj b/src/StreamFrame/StreamFrame.csproj index 6573cc0..8554559 100644 --- a/src/StreamFrame/StreamFrame.csproj +++ b/src/StreamFrame/StreamFrame.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + true @@ -23,6 +24,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/test/StreamFrame.Tests/StreamFrame.Tests.csproj b/test/StreamFrame.Tests/StreamFrame.Tests.csproj index ea6731f..d67f1b2 100644 --- a/test/StreamFrame.Tests/StreamFrame.Tests.csproj +++ b/test/StreamFrame.Tests/StreamFrame.Tests.csproj @@ -11,6 +11,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/test/StreamFrame.Tests/StreamingFrameEncodeTests.cs b/test/StreamFrame.Tests/StreamingFrameEncodeTests.cs index 2124357..2f11050 100644 --- a/test/StreamFrame.Tests/StreamingFrameEncodeTests.cs +++ b/test/StreamFrame.Tests/StreamingFrameEncodeTests.cs @@ -96,43 +96,3 @@ public void StxEtx_StreamingRejectsPayloadContainingStxOrEtx() Assert.Throws(() => framing.EndFrame(writer)); } } - -/// 测试用 IWrittenBufferWriter 实现(内部用数组增长)。 -internal sealed class TestWrittenBufferWriter : IWrittenBufferWriter, IDisposable -{ - private byte[] _buffer = new byte[16]; - private int _written; - - public Span WrittenSpan => _buffer.AsSpan(0, _written); - public int WrittenCount => _written; - - public void Advance(int count) - { - if (count < 0 || _written + count > _buffer.Length) - throw new ArgumentOutOfRangeException(nameof(count)); - _written += count; - } - - public Memory GetMemory(int sizeHint = 0) - { - EnsureCapacity(sizeHint); - return _buffer.AsMemory(_written); - } - - public Span GetSpan(int sizeHint = 0) - { - EnsureCapacity(sizeHint); - return _buffer.AsSpan(_written); - } - - private void EnsureCapacity(int sizeHint) - { - // GetSpan 不允许返回空 span,必须保证剩余空间 ≥ max(sizeHint, 1) - var required = _written + Math.Max(sizeHint, 1); - if (required <= _buffer.Length) - return; - Array.Resize(ref _buffer, Math.Max(required, _buffer.Length * 2)); - } - - public void Dispose() { } -} diff --git a/test/StreamFrame.Tests/TestWrittenBufferWriter.cs b/test/StreamFrame.Tests/TestWrittenBufferWriter.cs new file mode 100644 index 0000000..7b76882 --- /dev/null +++ b/test/StreamFrame.Tests/TestWrittenBufferWriter.cs @@ -0,0 +1,43 @@ +using System.Buffers; + +namespace StreamFrame.Tests; + +/// 测试用 IWrittenBufferWriter 实现(内部用数组增长)。 +internal sealed class TestWrittenBufferWriter : IWrittenBufferWriter, IDisposable +{ + private byte[] _buffer = new byte[16]; + private int _written; + + public Span WrittenSpan => _buffer.AsSpan(0, _written); + public int WrittenCount => _written; + + public void Advance(int count) + { + if (count < 0 || _written + count > _buffer.Length) + throw new ArgumentOutOfRangeException(nameof(count)); + _written += count; + } + + public Memory GetMemory(int sizeHint = 0) + { + EnsureCapacity(sizeHint); + return _buffer.AsMemory(_written); + } + + public Span GetSpan(int sizeHint = 0) + { + EnsureCapacity(sizeHint); + return _buffer.AsSpan(_written); + } + + private void EnsureCapacity(int sizeHint) + { + // GetSpan 不允许返回空 span,必须保证剩余空间 ≥ max(sizeHint, 1) + var required = _written + Math.Max(sizeHint, 1); + if (required <= _buffer.Length) + return; + Array.Resize(ref _buffer, Math.Max(required, _buffer.Length * 2)); + } + + public void Dispose() { } +} diff --git a/test/StreamFrame.Tests/XmlDocumentCodecTests.cs b/test/StreamFrame.Tests/XmlDocumentCodecTests.cs index 7808584..f2a1f02 100644 --- a/test/StreamFrame.Tests/XmlDocumentCodecTests.cs +++ b/test/StreamFrame.Tests/XmlDocumentCodecTests.cs @@ -18,8 +18,8 @@ public void EncodeThenDecode_RoundTripsDocument() // 编码结果应包含 XML 内容 var xmlText = Encoding.UTF8.GetString(writer.WrittenSpan); - Assert.Contains("", xmlText); - Assert.Contains("DeviceA", xmlText); + Assert.Contains("", xmlText, StringComparison.Ordinal); + Assert.Contains("DeviceA", xmlText, StringComparison.Ordinal); // 解码还原 var decoded = codec.Decode(new ReadOnlySequence(writer.WrittenSpan.ToArray()));