using System.Text; namespace GFramework.SourceGenerators.Tests.Core; /// /// 为源生成器测试提供轻量的源码标记解析能力。 /// public sealed class MarkupTestSource { private readonly SourceText _sourceText; private readonly IReadOnlyDictionary _spans; private MarkupTestSource( string source, SourceText sourceText, IReadOnlyDictionary spans) { Source = source; _sourceText = sourceText; _spans = spans; } /// /// 获取移除标记后的源码文本。 /// public string Source { get; } /// /// 解析形如 {|#0:identifier|} 的单层标记,并保留去标记后的源码。 /// /// 包含测试标记的源码。 /// 可用于测试输入和诊断定位的解析结果。 /// 标记格式不合法,或存在重复标记编号时抛出。 public static MarkupTestSource Parse(string markupSource) { var builder = new StringBuilder(markupSource.Length); var spans = new Dictionary(StringComparer.Ordinal); for (var index = 0; index < markupSource.Length; index++) { if (!StartsWithMarker(markupSource, index)) { builder.Append(markupSource[index]); continue; } index += 3; var markerIdStart = index; while (index < markupSource.Length && markupSource[index] != ':') index++; if (index >= markupSource.Length) throw new InvalidOperationException("Unterminated markup marker identifier."); var markerId = markupSource.Substring(markerIdStart, index - markerIdStart); if (markerId.Length == 0) throw new InvalidOperationException("Markup marker identifier cannot be empty."); var spanStart = builder.Length; index++; while (index < markupSource.Length && !EndsWithMarker(markupSource, index)) { builder.Append(markupSource[index]); index++; } if (index >= markupSource.Length) throw new InvalidOperationException($"Unterminated markup marker '{markerId}'."); if (!spans.TryAdd(markerId, TextSpan.FromBounds(spanStart, builder.Length))) throw new InvalidOperationException($"Duplicate markup marker '{markerId}'."); index++; } var source = builder.ToString(); return new MarkupTestSource(source, SourceText.From(source), spans); } /// /// 将标记位置应用到诊断断言,避免测试依赖硬编码行列号。 /// /// 要补全定位信息的诊断断言。 /// 标记编号。 /// 包含定位信息的诊断断言。 /// 指定标记不存在时抛出。 public DiagnosticResult WithSpan( DiagnosticResult diagnosticResult, string markerId) { var span = _spans[markerId]; var lineSpan = _sourceText.Lines.GetLinePositionSpan(span); return diagnosticResult.WithSpan( lineSpan.Start.Line + 1, lineSpan.Start.Character + 1, lineSpan.End.Line + 1, lineSpan.End.Character + 1); } private static bool StartsWithMarker( string text, int index) { return index + 3 < text.Length && text[index] == '{' && text[index + 1] == '|' && text[index + 2] == '#'; } private static bool EndsWithMarker( string text, int index) { return index + 1 < text.Length && text[index] == '|' && text[index + 1] == '}'; } }