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] == '}';
}
}