• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zorbathut / dec / 6055550688

02 Sep 2023 01:30AM UTC coverage: 92.051% (+0.02%) from 92.029%
6055550688

push

github-ci

Ben Rog-Wilhelm
Implement mode behaviors properly for Dictionary values.

3289 of 3573 relevant lines covered (92.05%)

115790.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.14
/src/ReaderXml.cs
1
namespace Dec
2
{
3
    using System;
4
    using System.Collections;
5
    using System.Collections.Generic;
6
    using System.IO;
7
    using System.Linq;
8
    using System.Reflection;
9
    using System.Xml;
10
    using System.Xml.Linq;
11

12
    internal class ReaderFileDecXml : ReaderFileDec
13
    {
14
        public static ReaderFileDecXml Create(TextReader input, string identifier)
15
        {
6,972✔
16
            XDocument doc;
17

18
            using (var _ = new CultureInfoScope(Config.CultureInfo))
6,972✔
19
            {
6,972✔
20
                try
21
                {
6,972✔
22
                    var settings = new XmlReaderSettings();
6,972✔
23
                    settings.IgnoreWhitespace = true;
6,972✔
24
                    using (var reader = XmlReader.Create(input, settings))
6,972✔
25
                    {
6,972✔
26
                        doc = XDocument.Load(reader, LoadOptions.SetLineInfo);
6,972✔
27
                    }
6,924✔
28
                }
6,924✔
29
                catch (System.Xml.XmlException e)
48✔
30
                {
48✔
31
                    Dbg.Ex(e);
48✔
32
                    return null;
48✔
33
                }
34
            }
6,924✔
35

36
            var result = new ReaderFileDecXml();
6,924✔
37
            result.doc = doc;
6,924✔
38
            result.fileIdentifier = identifier;
6,924✔
39
            return result;
6,924✔
40
        }
6,972✔
41

42
        public override List<ReaderDec> ParseDecs()
43
        {
6,921✔
44
            if (doc.Elements().Count() > 1)
6,921✔
45
            {
×
46
                // This isn't testable, unfortunately; XDocument doesn't even support multiple root elements.
47
                Dbg.Err($"{fileIdentifier}: Found {doc.Elements().Count()} root elements instead of the expected 1");
×
48
            }
×
49

50
            var result = new List<ReaderDec>();
6,921✔
51

52
            foreach (var rootElement in doc.Elements())
34,605✔
53
            {
6,921✔
54
                var rootContext = new InputContext(fileIdentifier, rootElement);
6,921✔
55
                if (rootElement.Name.LocalName != "Decs")
6,921✔
56
                {
12✔
57
                    Dbg.Wrn($"{rootContext}: Found root element with name `{rootElement.Name.LocalName}` when it should be `Decs`");
12✔
58
                }
12✔
59

60
                foreach (var decElement in rootElement.Elements())
75,339✔
61
                {
27,288✔
62
                    var readerDec = new ReaderDec();
27,288✔
63

64
                    readerDec.inputContext = new InputContext(fileIdentifier, decElement);
27,288✔
65
                    string typeName = decElement.Name.LocalName;
27,288✔
66

67
                    readerDec.type = UtilType.ParseDecFormatted(typeName, readerDec.inputContext);
27,288✔
68
                    if (readerDec.type == null || !typeof(Dec).IsAssignableFrom(readerDec.type))
27,288✔
69
                    {
24✔
70
                        Dbg.Err($"{readerDec.inputContext}: {typeName} is not a valid root Dec type");
24✔
71
                        continue;
24✔
72
                    }
73

74
                    if (decElement.Attribute("decName") == null)
27,264✔
75
                    {
12✔
76
                        Dbg.Err($"{readerDec.inputContext}: No dec name provided, add a `decName=` attribute to the {typeName} tag (example: <{typeName} decName=\"TheNameOfYour{typeName}\">)");
12✔
77
                        continue;
12✔
78
                    }
79

80
                    readerDec.name = decElement.Attribute("decName").Value;
27,252✔
81
                    if (!Util.ValidateDecName(readerDec.name, readerDec.inputContext))
27,252✔
82
                    {
48✔
83
                        continue;
48✔
84
                    }
85

86
                    // Consume decName so we know it's not hanging around
87
                    decElement.Attribute("decName").Remove();
27,204✔
88

89
                    // Check to see if we're abstract
90
                    {
27,204✔
91
                        var abstractAttribute = decElement.Attribute("abstract");
27,204✔
92
                        if (abstractAttribute != null)
27,204✔
93
                        {
672✔
94
                            bool abstrct;
95
                            if (!bool.TryParse(abstractAttribute.Value, out abstrct))
672✔
96
                            {
12✔
97
                                Dbg.Err($"{readerDec.inputContext}: Error encountered when parsing abstract attribute");
12✔
98
                            }
12✔
99
                            readerDec.abstrct = abstrct; // little dance to deal with the fact that readerDec.abstrct is a `bool?`
672✔
100

101
                            abstractAttribute.Remove();
672✔
102
                        }
672✔
103
                    }
27,204✔
104

105
                    // Get our parent info
106
                    {
27,204✔
107
                        var parentAttribute = decElement.Attribute("parent");
27,204✔
108
                        if (parentAttribute != null)
27,204✔
109
                        {
780✔
110
                            readerDec.parent = parentAttribute.Value;
780✔
111

112
                            parentAttribute.Remove();
780✔
113
                        }
780✔
114
                    }
27,204✔
115

116
                    // Everything looks good!
117
                    readerDec.node = new ReaderNodeXml(decElement, fileIdentifier);
27,204✔
118

119
                    result.Add(readerDec);
27,204✔
120
                }
27,204✔
121
            }
6,921✔
122

123
            return result;
6,921✔
124
        }
6,921✔
125

126
        private XDocument doc;
127
        private string fileIdentifier;
128
    }
129

130
    internal class ReaderFileRecorderXml : ReaderFileRecorder
131
    {
132
        public static ReaderFileRecorderXml Create(string input, string identifier)
133
        {
1,260✔
134
            XDocument doc;
135

136
            try
137
            {
1,260✔
138
                doc = XDocument.Parse(input, LoadOptions.SetLineInfo);
1,260✔
139
            }
1,251✔
140
            catch (System.Xml.XmlException e)
9✔
141
            {
9✔
142
                Dbg.Ex(e);
9✔
143
                return null;
9✔
144
            }
145

146
            if (doc.Elements().Count() > 1)
1,251✔
147
            {
×
148
                // This isn't testable, unfortunately; XDocument doesn't even support multiple root elements.
149
                Dbg.Err($"{identifier}: Found {doc.Elements().Count()} root elements instead of the expected 1");
×
150
            }
×
151

152
            var record = doc.Elements().First();
1,251✔
153
            if (record.Name.LocalName != "Record")
1,251✔
154
            {
3✔
155
                Dbg.Wrn($"{new InputContext(identifier, record)}: Found root element with name `{record.Name.LocalName}` when it should be `Record`");
3✔
156
            }
3✔
157

158
            var recordFormatVersion = record.ElementNamed("recordFormatVersion");
1,251✔
159
            if (recordFormatVersion == null)
1,251✔
160
            {
6✔
161
                Dbg.Err($"{new InputContext(identifier, record)}: Missing record format version, assuming the data is up-to-date");
6✔
162
            }
6✔
163
            else if (recordFormatVersion.GetText() != "1")
1,245✔
164
            {
9✔
165
                Dbg.Err($"{new InputContext(identifier, recordFormatVersion)}: Unknown record format version {recordFormatVersion.GetText()}, expected 1 or earlier");
9✔
166

167
                // I would rather not guess about this
168
                return null;
9✔
169
            }
170

171
            var result = new ReaderFileRecorderXml();
1,242✔
172
            result.record = record;
1,242✔
173
            result.fileIdentifier = identifier;
1,242✔
174

175
            return result;
1,242✔
176
        }
1,260✔
177

178
        public override List<ReaderRef> ParseRefs()
179
        {
1,242✔
180
            var result = new List<ReaderRef>();
1,242✔
181

182
            var refs = record.ElementNamed("refs");
1,242✔
183
            if (refs != null)
1,242✔
184
            {
498✔
185
                foreach (var reference in refs.Elements())
248,760✔
186
                {
123,633✔
187
                    var readerRef = new ReaderRef();
123,633✔
188

189
                    var context = new InputContext(fileIdentifier, reference);
123,633✔
190

191
                    if (reference.Name.LocalName != "Ref")
123,633✔
192
                    {
3✔
193
                        Dbg.Wrn($"{context}: Reference element should be named 'Ref'");
3✔
194
                    }
3✔
195

196
                    readerRef.id = reference.Attribute("id")?.Value;
123,633✔
197
                    if (readerRef.id == null)
123,633✔
198
                    {
3✔
199
                        Dbg.Err($"{context}: Missing reference ID");
3✔
200
                        continue;
3✔
201
                    }
202

203
                    // Further steps don't know how to deal with this, so we just strip it
204
                    reference.Attribute("id").Remove();
123,630✔
205

206
                    var className = reference.Attribute("class")?.Value;
123,630✔
207
                    if (className == null)
123,630✔
208
                    {
3✔
209
                        Dbg.Err($"{context}: Missing reference class name");
3✔
210
                        continue;
3✔
211
                    }
212

213
                    readerRef.type = (Type)Serialization.ParseString(className, typeof(Type), null, context);
123,627✔
214
                    if (readerRef.type.IsValueType)
123,627✔
215
                    {
3✔
216
                        Dbg.Err($"{context}: Reference assigned type {readerRef.type}, which is a value type");
3✔
217
                        continue;
3✔
218
                    }
219

220
                    readerRef.node = new ReaderNodeXml(reference, fileIdentifier);
123,624✔
221
                    result.Add(readerRef);
123,624✔
222
                }
123,624✔
223
            }
498✔
224

225
            return result;
1,242✔
226
        }
1,242✔
227

228
        public override ReaderNode ParseNode()
229
        {
1,242✔
230
            var data = record.ElementNamed("data");
1,242✔
231
            if (data == null)
1,242✔
232
            {
3✔
233
                Dbg.Err($"{new InputContext(fileIdentifier, record)}: No data element provided. This is not very recoverable.");
3✔
234

235
                return null;
3✔
236
            }
237

238
            return new ReaderNodeXml(data, fileIdentifier);
1,239✔
239
        }
1,242✔
240

241
        private XElement record;
242
        private string fileIdentifier;
243
    }
244

245
    internal class ReaderNodeXml : ReaderNode
246
    {
247
        public ReaderNodeXml(XElement xml, string fileIdentifier)
844,983✔
248
        {
844,983✔
249
            this.xml = xml;
844,983✔
250
            this.fileIdentifier = fileIdentifier;
844,983✔
251
        }
844,983✔
252

253
        public override InputContext GetInputContext()
254
        {
2,020,471✔
255
            return new InputContext(fileIdentifier, xml);
2,020,471✔
256
        }
2,020,471✔
257

258
        public override ReaderNode GetChildNamed(string name)
259
        {
380,343✔
260
            var child = xml.ElementNamed(name);
380,343✔
261
            return child == null ? null : new ReaderNodeXml(child, fileIdentifier);
380,343✔
262
        }
380,343✔
263

264
        public override string GetText()
265
        {
1,077,548✔
266
            return xml.GetText();
1,077,548✔
267
        }
1,077,548✔
268

269
        public override string GetMetadata(Metadata metadata)
270
        {
7,601,610✔
271
            return xml.Attribute(metadata.ToLowerString())?.Value;
7,601,610✔
272
        }
7,601,610✔
273

274
        private readonly HashSet<string> metadataNames = Util.GetEnumValues<Metadata>().Select(metadata => metadata.ToLowerString()).ToHashSet();
4,224,915✔
275
        public override string GetMetadataUnrecognized()
276
        {
844,608✔
277
            if (!xml.HasAttributes)
844,608✔
278
            {
394,007✔
279
                return null;
394,007✔
280
            }
281

282
            var unrecognized = string.Join(", ", xml.Attributes().Select(attr => attr.Name.LocalName).Where(name => !metadataNames.Contains(name)));
1,352,325✔
283
            return unrecognized == string.Empty ? null : unrecognized;
450,601✔
284
        }
844,608✔
285

286
        public override int GetChildCount()
287
        {
844,308✔
288
            return xml.Elements().Count();
844,308✔
289
        }
844,308✔
290

291
        public override void ParseList(IList list, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext)
292
        {
27,162✔
293
            var recorderChildContext = recorderContext.CreateChild();
27,162✔
294

295
            foreach (var fieldElement in xml.Elements())
209,514✔
296
            {
64,014✔
297
                if (fieldElement.Name.LocalName != "li")
64,014✔
298
                {
×
299
                    var elementContext = new InputContext(fileIdentifier, fieldElement);
×
300
                    Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
×
301
                }
×
302

303
                list.Add(Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(fieldElement, fileIdentifier) }, referencedType, null, readerContext, recorderChildContext));
64,014✔
304
            }
64,014✔
305
        }
27,162✔
306

307
        public override void ParseArray(Array array, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext, int startOffset)
308
        {
420✔
309
            var recorderChildContext = recorderContext.CreateChild();
420✔
310

311
            int i = 0;
420✔
312
            foreach (var fieldElement in xml.Elements())
4,572✔
313
            {
1,656✔
314
                if (fieldElement.Name.LocalName != "li")
1,656✔
315
                {
×
316
                    var elementContext = new InputContext(fileIdentifier, fieldElement);
×
317
                    Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
×
318
                }
×
319

320
                array.SetValue(Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(fieldElement, fileIdentifier) }, referencedType, null, readerContext, recorderChildContext), startOffset + i++);
1,656✔
321
            }
1,656✔
322
        }
420✔
323

324
        public override void ParseDictionary(IDictionary dict, Type referencedKeyType, Type referencedValueType, ReaderContext readerContext, Recorder.Context recorderContext, bool permitPatch)
325
        {
13,884✔
326
            var recorderChildContext = recorderContext.CreateChild();
13,884✔
327

328
            // avoid the heap allocation if we can
329
            var writtenFields = permitPatch ? new HashSet<object>() : null;
13,884✔
330

331
            foreach (var fieldElement in xml.Elements())
98,406✔
332
            {
28,377✔
333
                var elementContext = new InputContext(fileIdentifier, fieldElement);
28,377✔
334

335
                if (fieldElement.Name.LocalName == "li")
28,377✔
336
                {
27,390✔
337
                    // Treat this like a key/value pair
338
                    var keyNode = fieldElement.ElementNamedWithFallback("key", elementContext, "Dictionary includes li tag without a `key`");
27,390✔
339
                    var valueNode = fieldElement.ElementNamedWithFallback("value", elementContext, "Dictionary includes li tag without a `value`");
27,390✔
340

341
                    if (keyNode == null)
27,390✔
342
                    {
×
343
                        // error has already been generated
344
                        continue;
×
345
                    }
346

347
                    if (valueNode == null)
27,390✔
348
                    {
×
349
                        // error has already been generated
350
                        continue;
×
351
                    }
352

353
                    var key = Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(keyNode, fileIdentifier) }, referencedKeyType, null, readerContext, recorderChildContext);
27,390✔
354

355
                    if (key == null)
27,390✔
356
                    {
12✔
357
                        Dbg.Err($"{new InputContext(fileIdentifier, keyNode)}: Dictionary includes null key, skipping pair");
12✔
358
                        continue;
12✔
359
                    }
360

361
                    object originalValue = null;
27,378✔
362
                    if (dict.Contains(key))
27,378✔
363
                    {
24✔
364
                        // Annoyingly the IDictionary interface does not allow for simultaneous retrieval and existence check a la .TryGetValue(), so we get to do a second lookup here
365
                        // This is definitely not the common path, though, so, fine
366
                        originalValue = dict[key];
24✔
367

368
                        if (writtenFields == null || writtenFields.Contains(key))
24✔
369
                        {
×
370
                            Dbg.Err($"{elementContext}: Dictionary includes duplicate key `{key.ToString()}`");
×
371
                        }
×
372
                    }
24✔
373
                    
374
                    writtenFields?.Add(key);
27,378✔
375

376
                    dict[key] = Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(valueNode, fileIdentifier) }, referencedValueType, originalValue, readerContext, recorderChildContext);
27,378✔
377
                }
27,378✔
378
                else
379
                {
987✔
380
                    var key = Serialization.ParseString(fieldElement.Name.LocalName, referencedKeyType, null, elementContext);
987✔
381

382
                    if (key == null)
987✔
383
                    {
×
384
                        // it's really rare for this to happen, I think you could do it with a converter but that's it
385
                        Dbg.Err($"{elementContext}: Dictionary includes null key, skipping pair");
×
386

387
                        // just in case . . .
388
                        if (string.Compare(fieldElement.Name.LocalName, "li", true, System.Globalization.CultureInfo.InvariantCulture) == 0)
×
389
                        {
×
390
                            Dbg.Err($"{elementContext}: Did you mean to write `li`? This field is case-sensitive.");
×
391
                        }
×
392

393
                        continue;
×
394
                    }
395

396
                    object originalValue = null;
987✔
397
                    if (dict.Contains(key))
987✔
398
                    {
144✔
399
                        // Annoyingly the IDictionary interface does not allow for simultaneous retrieval and existence check a la .TryGetValue(), so we get to do a second lookup here
400
                        // This is definitely not the common path, though, so, fine
401
                        originalValue = dict[key];
144✔
402

403
                        if (writtenFields == null || writtenFields.Contains(key))
144✔
404
                        {
96✔
405
                            Dbg.Err($"{elementContext}: Dictionary includes duplicate key `{key.ToString()}`");
96✔
406
                        }
96✔
407
                    }
144✔
408

409
                    writtenFields?.Add(key);
987✔
410

411
                    dict[key] = Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(fieldElement, fileIdentifier) }, referencedValueType, originalValue, readerContext, recorderChildContext);
987✔
412
                }
987✔
413
            }
28,365✔
414
        }
13,884✔
415

416
        public override void ParseHashset(object hashset, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext, bool permitPatch)
417
        {
534✔
418
            var containsFunction = hashset.GetType().GetMethod("Contains");
534✔
419
            var addFunction = hashset.GetType().GetMethod("Add");
534✔
420

421
            var recorderChildContext = recorderContext.CreateChild();
534✔
422
            var keyParam = new object[1];   // this is just to cut down on GC churn
534✔
423

424
            // avoid the heap allocation if we can
425
            var writtenFields = permitPatch ? new HashSet<object>() : null;
534✔
426

427
            foreach (var fieldElement in xml.Elements())
5,454✔
428
            {
1,926✔
429
                var elementContext = new InputContext(fileIdentifier, fieldElement);
1,926✔
430

431
                // There's a potential bit of ambiguity here if someone does <li /> and expects that to be an actual string named "li".
432
                // Practically, I think this is less likely than someone doing <li></li> and expecting that to be the empty string.
433
                // And there's no other way to express the empty string.
434
                // So . . . we treat that like the empty string.
435
                if (fieldElement.Name.LocalName == "li")
1,926✔
436
                {
1,806✔
437
                    // Treat this like a full node
438
                    var key = Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(fieldElement, fileIdentifier) }, referencedType, null, readerContext, recorderChildContext);
1,806✔
439

440
                    if (key == null)
1,806✔
441
                    {
×
442
                        Dbg.Err($"{elementContext}: HashSet includes null key, skipping");
×
443
                        continue;
×
444
                    }
445

446
                    keyParam[0] = key;
1,806✔
447
                    
448
                    if ((bool)containsFunction.Invoke(hashset, keyParam) && (writtenFields == null || writtenFields.Contains(key)))
1,806✔
449
                    {
96✔
450
                        Dbg.Err($"{elementContext}: HashSet includes duplicate key `{key.ToString()}`");
96✔
451
                    }
96✔
452
                    writtenFields?.Add(key);
1,806✔
453

454
                    addFunction.Invoke(hashset, keyParam);
1,806✔
455
                }
1,806✔
456
                else
457
                {
120✔
458
                    if (fieldElement.HasElements)
120✔
459
                    {
×
460
                        Dbg.Err($"{elementContext}: HashSet non-li member includes data, ignoring");
×
461
                    }
×
462

463
                    var key = Serialization.ParseString(fieldElement.Name.LocalName, referencedType, null, elementContext);
120✔
464

465
                    if (key == null)
120✔
466
                    {
×
467
                        // it's really rare for this to happen, I think you could do it with a converter but that's it
468
                        Dbg.Err($"{elementContext}: HashSet includes null key, skipping pair");
×
469
                        continue;
×
470
                    }
471

472
                    keyParam[0] = key;
120✔
473

474
                    if ((bool)containsFunction.Invoke(hashset, keyParam) && (writtenFields == null || writtenFields.Contains(key)))
120✔
475
                    {
×
476
                        Dbg.Err($"{elementContext}: HashSet includes duplicate key `{key.ToString()}`");
×
477
                    }
×
478
                    writtenFields?.Add(key);
120✔
479

480
                    addFunction.Invoke(hashset, keyParam);
120✔
481
                }
120✔
482
            }
1,926✔
483
        }
534✔
484

485
        public override void ParseTuple(object[] parameters, Type referencedType, IList<string> parameterNames, ReaderContext readerContext, Recorder.Context recorderContext)
486
        {
600✔
487
            int expectedCount = referencedType.GenericTypeArguments.Length;
600✔
488
            var recorderChildContext = recorderContext.CreateChild();
600✔
489

490
            var elements = xml.Elements().ToList();
600✔
491

492
            bool hasNonLi = false;
600✔
493
            foreach (var elementField in elements)
5,172✔
494
            {
1,686✔
495
                if (elementField.Name.LocalName != "li")
1,686✔
496
                {
324✔
497
                    hasNonLi = true;
324✔
498
                }
324✔
499
            }
1,686✔
500

501
            if (!hasNonLi)
600✔
502
            {
486✔
503
                // Treat it like an indexed array
504

505
                if (elements.Count != parameters.Length)
486✔
506
                {
36✔
507
                    Dbg.Err($"{GetInputContext()}: Tuple expects {expectedCount} parameters but got {elements.Count}");
36✔
508
                }
36✔
509

510
                for (int i = 0; i < Math.Min(parameters.Length, elements.Count); ++i)
3,612✔
511
                {
1,320✔
512
                    parameters[i] = Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(elements[i], fileIdentifier) }, referencedType.GenericTypeArguments[i], null, readerContext, recorderChildContext);
1,320✔
513
                }
1,320✔
514

515
                // fill in anything missing
516
                for (int i = Math.Min(parameters.Length, elements.Count); i < parameters.Length; ++i)
1,044✔
517
                {
36✔
518
                    parameters[i] = Serialization.GenerateResultFallback(null, referencedType.GenericTypeArguments[i]);
36✔
519
                }
36✔
520
            }
486✔
521
            else
522
            {
114✔
523
                // We're doing named lookups instead
524
                if (parameterNames == null)
114✔
525
                {
24✔
526
                    parameterNames = Util.DefaultTupleNames;
24✔
527
                }
24✔
528

529
                if (parameterNames.Count < expectedCount)
114✔
530
                {
×
531
                    Dbg.Err($"{GetInputContext()}: Not enough tuple names (this honestly shouldn't even be possible)");
×
532

533
                    // TODO: handle it
534
                }
×
535

536
                bool[] seen = new bool[expectedCount];
114✔
537
                foreach (var elementItem in elements)
1,050✔
538
                {
354✔
539
                    var elementContext = new InputContext(fileIdentifier, elementItem);
354✔
540

541
                    int index = parameterNames.FirstIndexOf(n => n == elementItem.Name.LocalName);
1,374✔
542

543
                    if (index == -1)
354✔
544
                    {
48✔
545
                        Dbg.Err($"{elementContext}: Found field with unexpected name `{elementItem.Name.LocalName}`");
48✔
546
                        continue;
48✔
547
                    }
548

549
                    if (seen[index])
306✔
550
                    {
×
551
                        Dbg.Err($"{elementContext}: Found duplicate of field `{elementItem.Name.LocalName}`");
×
552
                    }
×
553

554
                    seen[index] = true;
306✔
555
                    parameters[index] = Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(elementItem, fileIdentifier) }, referencedType.GenericTypeArguments[index], null, readerContext, recorderChildContext);
306✔
556
                }
306✔
557

558
                for (int i = 0; i < seen.Length; ++i)
960✔
559
                {
366✔
560
                    if (!seen[i])
366✔
561
                    {
60✔
562
                        Dbg.Err($"{GetInputContext()}: Missing field with name `{parameterNames[i]}`");
60✔
563

564
                        // Patch it up as best we can
565
                        parameters[i] = Serialization.GenerateResultFallback(null, referencedType.GenericTypeArguments[i]);
60✔
566
                    }
60✔
567
                }
366✔
568
            }
114✔
569
        }
600✔
570

571
        public override void ParseReflection(object obj, ReaderContext readerContext, Recorder.Context recorderContext)
572
        {
53,108✔
573
            var recorderChildContext = recorderContext.CreateChild();
53,108✔
574
            var setFields = new HashSet<string>();
53,108✔
575

576
            var type = obj.GetType();
53,108✔
577

578
            foreach (var fieldElement in xml.Elements())
535,272✔
579
            {
187,983✔
580
                // Check for fields that have been set multiple times
581
                string fieldName = fieldElement.Name.LocalName;
187,983✔
582
                if (setFields.Contains(fieldName))
187,983✔
583
                {
24✔
584
                    Dbg.Err($"{new InputContext(fileIdentifier, fieldElement)}: Duplicate field `{fieldName}`");
24✔
585
                    // Just allow us to fall through; it's an error, but one with a reasonably obvious handling mechanism
586
                }
24✔
587
                setFields.Add(fieldName);
187,983✔
588

589
                var fieldElementInfo = type.GetFieldFromHierarchy(fieldName);
187,983✔
590
                if (fieldElementInfo == null)
187,983✔
591
                {
72✔
592
                    // Try to find a close match, if we can, just for a better error message
593
                    string match = null;
72✔
594
                    string canonicalFieldName = Util.LooseMatchCanonicalize(fieldName);
72✔
595

596
                    foreach (var testField in type.GetSerializableFieldsFromHierarchy())
396✔
597
                    {
108✔
598
                        if (Util.LooseMatchCanonicalize(testField.Name) == canonicalFieldName)
108✔
599
                        {
36✔
600
                            match = testField.Name;
36✔
601

602
                            // We could in theory do something overly clever where we try to find the best name, but I really don't care that much; this is meant as a quick suggestion, not an ironclad solution.
603
                            break;
36✔
604
                        }
605
                    }
72✔
606

607
                    if (match != null)
72✔
608
                    {
36✔
609
                        Dbg.Err($"{new InputContext(fileIdentifier, fieldElement)}: Field `{fieldName}` does not exist in type {type}; did you mean `{match}`?");
36✔
610
                    }
36✔
611
                    else
612
                    {
36✔
613
                        Dbg.Err($"{new InputContext(fileIdentifier, fieldElement)}: Field `{fieldName}` does not exist in type {type}");
36✔
614
                    }
36✔
615

616
                    continue;
72✔
617
                }
618

619
                if (fieldElementInfo.GetCustomAttribute<IndexAttribute>() != null)
187,911✔
620
                {
×
621
                    Dbg.Err($"{new InputContext(fileIdentifier, fieldElement)}: Attempting to set index field `{fieldName}`; these are generated by the dec system");
×
622
                    continue;
×
623
                }
624

625
                if (fieldElementInfo.GetCustomAttribute<NonSerializedAttribute>() != null)
187,911✔
626
                {
12✔
627
                    Dbg.Err($"{new InputContext(fileIdentifier, fieldElement)}: Attempting to set nonserialized field `{fieldName}`");
12✔
628
                    continue;
12✔
629
                }
630

631
                fieldElementInfo.SetValue(obj, Serialization.ParseElement(new List<ReaderNode>() { new ReaderNodeXml(fieldElement, fileIdentifier) }, fieldElementInfo.FieldType, fieldElementInfo.GetValue(obj), readerContext, recorderChildContext, fieldInfo: fieldElementInfo));
187,899✔
632
            }
187,899✔
633
        }
53,108✔
634

635
        private XElement xml;
636
        private string fileIdentifier;
637
    }
638
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc