Skip to content

Commit d05cc9f

Browse files
committed
Add SerializeWithSourceMap<T>
1 parent b0f2917 commit d05cc9f

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

ValveKeyValue/ValveKeyValue.Test/SourceMapTestCase.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,55 @@ public void Kv1SerializerSpansLineUpWithEmittedText()
9999
Assert.That(spans.Any(s => s.TokenType == KVTokenType.ObjectEnd && text[s.Start] == '}'), Is.True);
100100
}
101101

102+
[Test]
103+
public void Kv1TypedSerializerSpansLineUpWithEmittedText()
104+
{
105+
var person = new Person { FirstName = "Alice", Age = 30 };
106+
107+
var (text, spans) = KVSerializer.Create(KVSerializationFormat.KeyValues1Text)
108+
.SerializeWithSourceMap(person, name: "person");
109+
110+
AssertSpansAreWellFormed(text, spans);
111+
112+
AssertSpanExists(text, spans, KVTokenType.Key, "\"person\"");
113+
AssertSpanExists(text, spans, KVTokenType.Key, "\"FirstName\"");
114+
AssertSpanExists(text, spans, KVTokenType.Key, "\"Age\"");
115+
AssertSpanExists(text, spans, KVTokenType.String, "\"Alice\"");
116+
AssertSpanExists(text, spans, KVTokenType.String, "\"30\"");
117+
118+
Assert.That(spans.Any(s => s.TokenType == KVTokenType.ObjectStart && text[s.Start] == '{'), Is.True);
119+
Assert.That(spans.Any(s => s.TokenType == KVTokenType.ObjectEnd && text[s.Start] == '}'), Is.True);
120+
}
121+
122+
[Test]
123+
public void Kv3TypedSerializerSpansLineUpWithEmittedText()
124+
{
125+
var person = new Person { FirstName = "Alice", Age = 30 };
126+
127+
var (text, spans) = KVSerializer.Create(KVSerializationFormat.KeyValues3Text)
128+
.SerializeWithSourceMap(person, name: "person");
129+
130+
AssertSpansAreWellFormed(text, spans);
131+
132+
// Header is emitted by the KV3 serializer regardless of input shape.
133+
Assert.That(spans[0].TokenType, Is.EqualTo(KVTokenType.Header));
134+
Assert.That(text.AsSpan(spans[0].Start, spans[0].End - spans[0].Start).StartsWith("<!--"), Is.True);
135+
136+
AssertSpanExists(text, spans, KVTokenType.Key, "FirstName");
137+
AssertSpanExists(text, spans, KVTokenType.Key, "Age");
138+
AssertSpanExists(text, spans, KVTokenType.String, "\"Alice\"");
139+
AssertSpanExists(text, spans, KVTokenType.Identifier, "30");
140+
141+
Assert.That(spans.Any(s => s.TokenType == KVTokenType.ObjectStart && text[s.Start] == '{'), Is.True);
142+
Assert.That(spans.Any(s => s.TokenType == KVTokenType.ObjectEnd && text[s.Start] == '}'), Is.True);
143+
}
144+
145+
class Person
146+
{
147+
public required string FirstName { get; set; }
148+
public int Age { get; set; }
149+
}
150+
102151
[Test]
103152
public void Kv1ParserSpansLineUpWithInputText()
104153
{

ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ public class ValveKeyValue.KVSerializer
267267
public void Serialize<ValveKeyValue.TData>(System.IO.Stream stream, ValveKeyValue.TData data, string name, ValveKeyValue.KVSerializerOptions options);
268268
public ValueTuple`2[[string, System.Collections.Generic.IReadOnlyList`1[[ValveKeyValue.KvSourceSpan]]]] SerializeWithSourceMap(ValveKeyValue.KVDocument data, ValveKeyValue.KVSerializerOptions options);
269269
public ValueTuple`2[[string, System.Collections.Generic.IReadOnlyList`1[[ValveKeyValue.KvSourceSpan]]]] SerializeWithSourceMap(ValveKeyValue.KVObject data, string name, ValveKeyValue.KVSerializerOptions options);
270+
public ValueTuple`2[[string, System.Collections.Generic.IReadOnlyList`1[[ValveKeyValue.KvSourceSpan]]]] SerializeWithSourceMap<ValveKeyValue.TData>(ValveKeyValue.TData data, string name, ValveKeyValue.KVSerializerOptions options);
270271
public string ToString();
271272
}
272273

ValveKeyValue/ValveKeyValue/KVSerializer.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,25 @@ IVisitingReader MakeSourceMapReader(TextReader textReader, IParsingVisitationLis
186186
return SerializeWithSourceMapCore(data, name, header: null, options);
187187
}
188188

189+
/// <summary>
190+
/// Serializes a typed object to KeyValues text and produces a per-token source map alongside it.
191+
/// Intended for syntax highlighters that want exact token boundaries instead of regex
192+
/// approximations. Only text formats are supported.
193+
/// </summary>
194+
/// <param name="data">The object to serialize.</param>
195+
/// <param name="name">The top-level object name.</param>
196+
/// <param name="options">Options to use that can influence the serialization process.</param>
197+
/// <typeparam name="TData">The type of object to serialize.</typeparam>
198+
/// <returns>The serialized text and a list of <see cref="KvSourceSpan"/> records covering each token.</returns>
199+
public (string Text, IReadOnlyList<KvSourceSpan> Spans) SerializeWithSourceMap<[DynamicallyAccessedMembers(Trimming.Properties)] TData>(TData data, string name, KVSerializerOptions? options = null)
200+
{
201+
ArgumentNullException.ThrowIfNull(data);
202+
ArgumentNullException.ThrowIfNull(name);
203+
204+
var kvObjectTree = ObjectCopier.FromObject(typeof(TData), data);
205+
return SerializeWithSourceMapCore(kvObjectTree, name, header: null, options);
206+
}
207+
189208
(string Text, IReadOnlyList<KvSourceSpan> Spans) SerializeWithSourceMapCore(KVObject root, string? name, KVHeader? header, KVSerializerOptions? options)
190209
{
191210
var resolvedOptions = options ?? KVSerializerOptions.DefaultOptions;

0 commit comments

Comments
 (0)