Skip to content
Open
24 changes: 24 additions & 0 deletions UnityXrefMaps.Tests/UtilsTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
68 changes: 68 additions & 0 deletions UnityXrefMaps.Tests/XrefMapServiceTests.cs
Original file line number Diff line number Diff line change
@@ -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<XrefMapService>.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);
}
}
}
152 changes: 76 additions & 76 deletions UnityXrefMaps/XrefMapService.cs
Original file line number Diff line number Diff line change
@@ -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<XrefMapService> logger)
{
private readonly Deserializer _deserializer = new();
private readonly Serializer _serializer = new();

/// <summary>
/// Loads a <see cref="XrefMap"/> from a file.
/// </summary>
/// <param name="filePath">The path of the file.</param>
/// <returns>The loaded <see cref="XrefMap"/> from <paramref name="filePath"/>.</returns>
public async Task<XrefMap> 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<XrefMap>(xrefMapText);
}

/// <summary>
/// Fix the <see cref="XrefMapReference.Href"/> of <see cref="References"/> of this <see cref="XrefMap"/>.
/// </summary>
/// <param name="apiUrl">The URL of the online API documentation of Unity.</param>
public IEnumerable<XrefMapReference> Process(string apiUrl, IEnumerable<XrefMapReference> references, IEnumerable<string> hrefNamespacesToTrim, bool isPackage)
{
List<XrefMapReference> 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;
}

/// <summary>
/// Saves this <see cref="XrefMap"/> to a file.
/// </summary>
/// <param name="filePath">The path of the file.</param>
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<XrefMapService> logger)
{
private readonly Deserializer _deserializer = new();
private readonly Serializer _serializer = new();
/// <summary>
/// Loads a <see cref="XrefMap"/> from a file.
/// </summary>
/// <param name="filePath">The path of the file.</param>
/// <returns>The loaded <see cref="XrefMap"/> from <paramref name="filePath"/>.</returns>
public async Task<XrefMap> 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<XrefMap>(xrefMapText);
}
/// <summary>
/// Fix the <see cref="XrefMapReference.Href"/> of <see cref="References"/> of this <see cref="XrefMap"/>.
/// </summary>
/// <param name="apiUrl">The URL of the online API documentation of Unity.</param>
public IEnumerable<XrefMapReference> Process(string apiUrl, IEnumerable<XrefMapReference> references, IEnumerable<string> hrefNamespacesToTrim, bool isPackage)
{
List<XrefMapReference> 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;
}
/// <summary>
/// Saves this <see cref="XrefMap"/> to a file.
/// </summary>
/// <param name="filePath">The path of the file.</param>
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();
}
}
Loading