feat(commons): TryParseRelayBody — detect pure ctx.GetTag relay scripts
This commit is contained in:
@@ -32,6 +32,13 @@ public static class EquipmentScriptPaths
|
||||
private static readonly Regex PathLiteralRegex =
|
||||
new(@"(ctx\s*\.\s*(?:GetTag|SetVirtualTag)\s*\(\s*"")([^""]*)("")", RegexOptions.Compiled);
|
||||
|
||||
// A pure pass-through virtual-tag body: exactly `return ctx.GetTag("<ref>").Value;`
|
||||
// (whitespace-insensitive). Captures <ref> for relay→alias conversion. Anything with extra
|
||||
// statements, arithmetic, a different member than .Value, or multiple GetTag calls is NOT a relay.
|
||||
private static readonly Regex RelayBodyRegex = new(
|
||||
@"^\s*return\s+ctx\s*\.\s*GetTag\s*\(\s*""([^""]+)""\s*\)\s*\.\s*Value\s*;\s*$",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
/// <summary>True when the source uses the <c>{{equip}}</c> token anywhere.</summary>
|
||||
/// <param name="source">The script source to scan.</param>
|
||||
public static bool ContainsEquipToken(string? source) =>
|
||||
@@ -136,4 +143,30 @@ public static class EquipmentScriptPaths
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recognise a pure relay virtual-tag body — one whose entire body is exactly
|
||||
/// <c>return ctx.GetTag("<paramref name="tagReference"/>").Value;</c> (whitespace-insensitive) —
|
||||
/// and capture its single <c>GetTag</c> reference. Bodies with extra statements, arithmetic,
|
||||
/// a member other than <c>.Value</c>, or multiple <c>GetTag</c> calls are <em>not</em> relays.
|
||||
/// </summary>
|
||||
/// <param name="source">The virtual-tag script source to inspect.</param>
|
||||
/// <param name="tagReference">
|
||||
/// When the method returns <see langword="true"/>, the captured <c>GetTag</c> path literal
|
||||
/// (e.g. <c>TestMachine_020.TestChangingInt</c> or <c>{{equip}}.Speed</c>);
|
||||
/// otherwise <see langword="null"/>.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> when <paramref name="source"/> is a pure pass-through relay;
|
||||
/// <see langword="false"/> for any other body or <see langword="null"/>/<see langword="whitespace"/> input.
|
||||
/// </returns>
|
||||
public static bool TryParseRelayBody(string? source, out string? tagReference)
|
||||
{
|
||||
tagReference = null;
|
||||
if (string.IsNullOrWhiteSpace(source)) return false;
|
||||
var m = RelayBodyRegex.Match(source);
|
||||
if (!m.Success) return false;
|
||||
tagReference = m.Groups[1].Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,4 +211,29 @@ public class EquipmentScriptPathsTests
|
||||
{
|
||||
EquipmentScriptPaths.ContainsEquipToken(null).ShouldBeFalse();
|
||||
}
|
||||
|
||||
// ---- TryParseRelayBody ----
|
||||
|
||||
[Theory]
|
||||
[InlineData("return ctx.GetTag(\"TestMachine_020.TestChangingInt\").Value;", "TestMachine_020.TestChangingInt")]
|
||||
[InlineData(" return ctx . GetTag ( \"A.B\" ) . Value ; ", "A.B")]
|
||||
[InlineData("return ctx.GetTag(\"{{equip}}.Speed\").Value;", "{{equip}}.Speed")]
|
||||
public void TryParseRelayBody_accepts_exact_passthrough(string src, string expectedRef)
|
||||
{
|
||||
EquipmentScriptPaths.TryParseRelayBody(src, out var r).ShouldBeTrue();
|
||||
r.ShouldBe(expectedRef);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("return ctx.GetTag(\"A.B\").Value * 2;")]
|
||||
[InlineData("var x = ctx.GetTag(\"A.B\").Value; return x;")]
|
||||
[InlineData("return ctx.GetTag(\"A.B\").Value + ctx.GetTag(\"C.D\").Value;")]
|
||||
[InlineData("return ctx.GetTag(\"A.B\").Quality;")]
|
||||
[InlineData("return 5;")]
|
||||
[InlineData("")]
|
||||
public void TryParseRelayBody_rejects_non_relay(string src)
|
||||
{
|
||||
EquipmentScriptPaths.TryParseRelayBody(src, out var r).ShouldBeFalse();
|
||||
r.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user