diff --git a/UnityXrefMaps.Tests/UtilsTests.cs b/UnityXrefMaps.Tests/UtilsTests.cs new file mode 100644 index 0000000..88b78dc --- /dev/null +++ b/UnityXrefMaps.Tests/UtilsTests.cs @@ -0,0 +1,24 @@ +using System.Threading.Tasks; + +namespace UnityXrefMaps.Tests; + +public class UtilsTests +{ + [Fact] + public async Task RunCommand_SuccessfulProcess_ReturnsZero() + { + // `true` always exits with code 0. + // RunCommand must surface the exit code so callers can detect failures. + int exitCode = await Utils.RunCommand("true", "", _ => { }, _ => { }); + Assert.Equal(0, exitCode); + } + + [Fact] + public async Task RunCommand_FailingProcess_ReturnsNonZero() + { + // `false` always exits with code 1. + // Before the fix, RunCommand returned void so exit codes were silently discarded. + int exitCode = await Utils.RunCommand("false", "", _ => { }, _ => { }); + Assert.Equal(1, exitCode); + } +} diff --git a/UnityXrefMaps.Tests/XrefMapServiceTests.cs b/UnityXrefMaps.Tests/XrefMapServiceTests.cs new file mode 100644 index 0000000..396371e --- /dev/null +++ b/UnityXrefMaps.Tests/XrefMapServiceTests.cs @@ -0,0 +1,68 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging.Abstractions; + +namespace UnityXrefMaps.Tests; + +public class XrefMapServiceTests +{ + private readonly XrefMapService _service = new(NullLogger.Instance); + + [Fact] + public async Task Load_ZeroColonAtWordBoundary_IsStripped() + { + // `0: value` in a YAML string value causes the parser to treat it as a nested + // mapping, crashing with a type-mismatch. The regex must strip `0:` before parsing. + string yaml = """ + sorted: false + references: + - uid: SomeType + name: SomeType + href: SomeType.html + commentId: T:SomeType + nameWithType: 0: orphan + """; + + string filePath = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(filePath, yaml); + XrefMap result = await _service.Load(filePath); + Assert.NotNull(result); + Assert.Single(result.References!); + } + finally + { + File.Delete(filePath); + } + } + + [Fact] + public async Task Load_NonZeroDigitColon_IsPreserved() + { + // The old regex `(\d):` stripped the colon from every digit-colon sequence, corrupting + // legitimate values such as `data1:field` → `data1field` in href or nameWithType fields. + // Only `0:` at a word boundary should be stripped. + string yaml = """ + sorted: false + references: + - uid: SomeType + name: SomeType + href: SomeType.html + commentId: T:SomeType + nameWithType: data1:field + """; + + string filePath = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(filePath, yaml); + XrefMap result = await _service.Load(filePath); + Assert.Equal("data1:field", result.References![0].NameWithType); + } + finally + { + File.Delete(filePath); + } + } +} diff --git a/UnityXrefMaps/XrefMapService.cs b/UnityXrefMaps/XrefMapService.cs index 4b26ee7..c460a18 100644 --- a/UnityXrefMaps/XrefMapService.cs +++ b/UnityXrefMaps/XrefMapService.cs @@ -1,76 +1,76 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using YamlDotNet.Serialization; - -namespace UnityXrefMaps -{ - internal sealed partial class XrefMapService(ILogger logger) - { - private readonly Deserializer _deserializer = new(); - private readonly Serializer _serializer = new(); - - /// - /// Loads a from a file. - /// - /// The path of the file. - /// The loaded from . - public async Task Load(string filePath, CancellationToken cancellationToken = default) - { - string xrefMapText = await File.ReadAllTextAsync(filePath, cancellationToken); - - // Remove `0:` strings on the xrefmap that make crash Deserializer - xrefMapText = ZeroStringsRegex().Replace(xrefMapText, "$1"); - - return _deserializer.Deserialize(xrefMapText); - } - - /// - /// Fix the of of this . - /// - /// The URL of the online API documentation of Unity. - public IEnumerable Process(string apiUrl, IEnumerable references, IEnumerable hrefNamespacesToTrim, bool isPackage) - { - List fixedReferences = []; - - foreach (XrefMapReference reference in references) - { - if (!reference.IsValid) - { - continue; - } - - try - { - reference.Href = XRefHrefFixer.Fix(apiUrl, reference, hrefNamespacesToTrim, isPackage); - - fixedReferences.Add(reference); - } - catch (Exception e) - { - logger.LogWarning(e, "Error fixing href: {Uid}", reference.Uid); - } - } - - return fixedReferences; - } - - /// - /// Saves this to a file. - /// - /// The path of the file. - public async Task Save(string filePath, XrefMap xrefMap, CancellationToken cancellationToken = default) - { - string xrefMapText = "### YamlMime:XRefMap\n" + _serializer.Serialize(xrefMap); - - await File.WriteAllTextAsync(filePath, xrefMapText, cancellationToken); - } - - [GeneratedRegex(@"(\d):")] - private static partial Regex ZeroStringsRegex(); - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; + +namespace UnityXrefMaps +{ + internal sealed partial class XrefMapService(ILogger logger) + { + private readonly Deserializer _deserializer = new(); + private readonly Serializer _serializer = new(); + + /// + /// Loads a from a file. + /// + /// The path of the file. + /// The loaded from . + public async Task Load(string filePath, CancellationToken cancellationToken = default) + { + string xrefMapText = await File.ReadAllTextAsync(filePath, cancellationToken); + + // Remove `0:` strings on the xrefmap that make crash Deserializer + xrefMapText = ZeroStringsRegex().Replace(xrefMapText, "0"); + + return _deserializer.Deserialize(xrefMapText); + } + + /// + /// Fix the of of this . + /// + /// The URL of the online API documentation of Unity. + public IEnumerable Process(string apiUrl, IEnumerable references, IEnumerable hrefNamespacesToTrim, bool isPackage) + { + List fixedReferences = []; + + foreach (XrefMapReference reference in references) + { + if (!reference.IsValid) + { + continue; + } + + try + { + reference.Href = XRefHrefFixer.Fix(apiUrl, reference, hrefNamespacesToTrim, isPackage); + + fixedReferences.Add(reference); + } + catch (Exception e) + { + logger.LogWarning(e, "Error fixing href: {Uid}", reference.Uid); + } + } + + return fixedReferences; + } + + /// + /// Saves this to a file. + /// + /// The path of the file. + public async Task Save(string filePath, XrefMap xrefMap, CancellationToken cancellationToken = default) + { + string xrefMapText = "### YamlMime:XRefMap\n" + _serializer.Serialize(xrefMap); + + await File.WriteAllTextAsync(filePath, xrefMapText, cancellationToken); + } + + [GeneratedRegex(@"\b0:")] + private static partial Regex ZeroStringsRegex(); + } +}