Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b3e21a2
Initial commit with task details
konard Dec 13, 2025
5690ace
feat(js): Add multi-reference support for unquoted multi-word IDs
konard Dec 13, 2025
f0cc61c
feat(python): Add multi-reference support for unquoted multi-word IDs
konard Dec 13, 2025
cf65c12
feat(rust): Add multi-reference support for issue #184
konard Dec 13, 2025
c27b3f0
feat(csharp): Add multi-reference support for issue #184
konard Dec 13, 2025
3a83202
style: Fix formatting issues in JS, Python, and Rust
konard Dec 13, 2025
338ad15
fix(python): Remove unused 'Any' import from link.py
konard Dec 13, 2025
93096a3
Revert "Initial commit with task details"
konard Dec 13, 2025
9a36b7e
refactor(js,python): Use ids array as primary storage with id as comp…
konard Dec 14, 2025
2582526
style: Fix Prettier formatting in MultiRefTests.test.js
konard Dec 14, 2025
b165747
Merge remote-tracking branch 'origin/main' into issue-184-e00f7290354f
konard Dec 23, 2025
20ed732
feat(go,java): Add multi-reference tests and fix formatters
konard Dec 23, 2025
02421b2
style: Fix Go formatting (gofmt)
konard Dec 23, 2025
d0c0e9c
fix(java): Update test expectation for multi-word ID formatting
konard Dec 23, 2025
3d56796
fix(c#,java,go): Preserve quotes for single-reference IDs with spaces
konard Dec 23, 2025
5d8afbd
refactor(rust): Replace id_string with id/ids methods on LiNo
konard Dec 23, 2025
bc064ef
style(rust): Fix formatting (rustfmt)
konard Dec 23, 2025
81dd73b
feat(csharp): Add Ids property with backward-compatible Id for multi-…
konard Dec 27, 2025
1fd7b03
feat(go,java): Add ids/id API for multi-reference support
konard Dec 27, 2025
deed344
style(java): Apply Spotless formatting
konard Dec 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion csharp/Link.Foundation.Links.Notation.Tests/ApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
var values = new List<Link<string>> { new Link<string>("child", null) };
var link = new Link<string>("id", values);
Assert.Equal("id", link.Id);
Assert.Single(link.Values);

Check warning on line 25 in csharp/Link.Foundation.Links.Notation.Tests/ApiTests.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference argument for parameter 'collection' in 'Link<string> Assert.Single<Link<string>>(IEnumerable<Link<string>> collection)'.
Assert.Equal("child", link.Values?[0].Id);
}

Expand All @@ -49,7 +49,7 @@
[Fact]
public static void EmptyLinkTest()
{
var link = new Link<string>(null, new List<Link<string>>());
var link = new Link<string>((string?)null, new List<Link<string>>());
var output = link.ToString();
Assert.Equal("()", output);
}
Expand Down
4 changes: 2 additions & 2 deletions csharp/Link.Foundation.Links.Notation.Tests/LinkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static void LinkToStringWithIdOnlyTest()
public static void LinkToStringWithValuesOnlyTest()
{
var values = new List<Link<string>> { new Link<string>("value1"), new Link<string>("value2") };
var link = new Link<string>(null, values);
var link = new Link<string>((string?)null, values);
Assert.Equal("(value1 value2)", link.ToString());
}

Expand Down Expand Up @@ -97,7 +97,7 @@ public static void LinkSimplifyTest()
Assert.Equal(link1, simplified1);

// Test simplify with single value
var link2 = new Link<string>(null, new List<Link<string>> { new Link<string>("single", null) });
var link2 = new Link<string>((string?)null, new List<Link<string>> { new Link<string>("single", null) });
var simplified2 = link2.Simplify();
Assert.Equal("single", simplified2.Id);
Assert.Null(simplified2.Values);
Expand Down
177 changes: 177 additions & 0 deletions csharp/Link.Foundation.Links.Notation.Tests/MultiRefTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using System;
using System.Linq;
using Xunit;

namespace Link.Foundation.Links.Notation.Tests
{
/// <summary>
/// Multi-Reference Feature Tests (Issue #184)
/// Tests for multi-word references without quotes:
/// - (some example: some example is a link)
/// - Ids as array: ["some", "example"]
/// - Id property throws for multi-refs, use Ids instead
/// </summary>
public static class MultiRefTests
{
[Fact]
public static void ParsesTwoWordMultiReferenceId()
{
var parser = new Parser();
var result = parser.Parse("(some example: value)");
Assert.Single(result);
// Use Ids property for multi-references
Assert.NotNull(result[0].Ids);
Assert.Equal(2, result[0].Ids.Count);

Check warning on line 24 in csharp/Link.Foundation.Links.Notation.Tests/MultiRefTests.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
Assert.Equal(new[] { "some", "example" }, result[0].Ids);
Assert.Single(result[0].Values);

Check warning on line 26 in csharp/Link.Foundation.Links.Notation.Tests/MultiRefTests.cs

View workflow job for this annotation

GitHub Actions / test

Possible null reference argument for parameter 'collection' in 'Link<string> Assert.Single<Link<string>>(IEnumerable<Link<string>> collection)'.
}

[Fact]
public static void ParsesThreeWordMultiReferenceId()
{
var parser = new Parser();
var result = parser.Parse("(new york city: value)");
Assert.Single(result);
Assert.NotNull(result[0].Ids);
Assert.Equal(3, result[0].Ids.Count);

Check warning on line 36 in csharp/Link.Foundation.Links.Notation.Tests/MultiRefTests.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.
Assert.Equal(new[] { "new", "york", "city" }, result[0].Ids);
}

[Fact]
public static void IdPropertyThrowsForMultiRef()
{
var parser = new Parser();
var result = parser.Parse("(some example: value)");
Assert.Single(result);
// Id property should throw MultiReferenceException for multi-refs
var ex = Assert.Throws<MultiReferenceException>(() => result[0].Id);
Assert.Equal(2, ex.ReferenceCount);
Assert.Contains("Use the 'Ids' property instead of 'Id'", ex.Message);
}

[Fact]
public static void ParsesSingleWordIdBackwardCompatible()
{
var parser = new Parser();
var result = parser.Parse("(papa: value)");
Assert.Single(result);
// Single-word: Id returns string, Ids returns array with single element
Assert.Equal("papa", result[0].Id);
Assert.NotNull(result[0].Ids);
Assert.Single(result[0].Ids);
Assert.Equal(new[] { "papa" }, result[0].Ids);
}

[Fact]
public static void ParsesQuotedMultiWordIdBackwardCompatible()
{
var parser = new Parser();
var result = parser.Parse("('some example': value)");
Assert.Single(result);
// Quoted multi-word is a single reference, so Id works
Assert.Equal("some example", result[0].Id);
Assert.NotNull(result[0].Ids);
Assert.Single(result[0].Ids);
Assert.Equal(new[] { "some example" }, result[0].Ids);
}

[Fact]
public static void FormatMultiReferenceId()
{
var parser = new Parser();
var result = parser.Parse("(some example: value)");
var formatted = result.Format();
// Multi-reference IDs are formatted with quotes (normalized form)
Assert.Equal("('some example': value)", formatted);
}

[Fact]
public static void RoundTripMultiReference()
{
var parser = new Parser();
var input = "(new york city: great)";
var result = parser.Parse(input);
var formatted = result.Format();
// Round-trip normalizes multi-word ID to quoted form
Assert.Equal("('new york city': great)", formatted);
}

[Fact]
public static void ParsesIndentedSyntaxMultiReference()
{
var parser = new Parser();
var input = "some example:\n value1\n value2";
var result = parser.Parse(input);
Assert.Single(result);
Assert.NotNull(result[0].Ids);
Assert.Equal(new[] { "some", "example" }, result[0].Ids);
Assert.Equal(2, result[0].Values?.Count);
}

[Fact]
public static void BackwardCompatibilitySingleLine()
{
var parser = new Parser();
var result = parser.Parse("papa: loves mama");
Assert.Single(result);
Assert.Equal("papa", result[0].Id);
Assert.Equal(2, result[0].Values?.Count);
}

[Fact]
public static void BackwardCompatibilityParenthesized()
{
var parser = new Parser();
var result = parser.Parse("(papa: loves mama)");
Assert.Single(result);
Assert.Equal("papa", result[0].Id);
Assert.Equal(2, result[0].Values?.Count);
}

[Fact]
public static void BackwardCompatibilityNested()
{
var parser = new Parser();
var result = parser.Parse("(outer: (inner: value))");
Assert.Single(result);
Assert.Equal("outer", result[0].Id);
Assert.Single(result[0].Values);
Assert.Equal("inner", result[0].Values?[0].Id);
}

[Fact]
public static void MultiRefWithMultipleValues()
{
var parser = new Parser();
var result = parser.Parse("(some example: one two three)");
Assert.Single(result);
// Ids should contain the multi-ref parts
Assert.NotNull(result[0].Ids);
Assert.Equal(new[] { "some", "example" }, result[0].Ids);
// Id should throw for multi-ref
Assert.Throws<MultiReferenceException>(() => result[0].Id);
// Values should be 3 separate references
Assert.Equal(3, result[0].Values?.Count);
Assert.Equal("one", result[0].Values?[0].Id);
Assert.Equal("two", result[0].Values?[1].Id);
Assert.Equal("three", result[0].Values?[2].Id);
}

[Fact]
public static void MultiRefValuesAreSeparateReferences()
{
// Per issue #184 feedback: context-aware parsing is out of scope
var parser = new Parser();
var result = parser.Parse("(some example: some example is a link)");
Assert.NotNull(result[0].Ids);
Assert.Equal(new[] { "some", "example" }, result[0].Ids);
// Values should be 5 separate references (no context-aware grouping)
Assert.Equal(5, result[0].Values?.Count);
Assert.Equal("some", result[0].Values?[0].Id);
Assert.Equal("example", result[0].Values?[1].Id);
Assert.Equal("is", result[0].Values?[2].Id);
Assert.Equal("a", result[0].Values?[3].Id);
Assert.Equal("link", result[0].Values?[4].Id);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private static void CollectLinksWithIndentedIdSyntaxSupport<TLinkAddress>(List<L

if (groups != null && groups.Count > 0)
{
bool isIndentedIdSyntax = link.Id != null &&
bool isIndentedIdSyntax = (link.Ids != null && link.Ids.Count > 0) &&
(link.Values == null || link.Values.Count == 0);

if (isIndentedIdSyntax && !parentDependency.HasValue)
Expand All @@ -44,7 +44,7 @@ private static void CollectLinksWithIndentedIdSyntaxSupport<TLinkAddress>(List<L
childValues.Add(transformedLink);
}

var linkWithChildren = new Link<TLinkAddress>(link.Id, childValues);
var linkWithChildren = new Link<TLinkAddress>(link.Ids, childValues);
list.Add(linkWithChildren);
}
else
Expand All @@ -56,7 +56,7 @@ private static void CollectLinksWithIndentedIdSyntaxSupport<TLinkAddress>(List<L
var childLink = childGroup.Link;
var childGroups = childGroup.Groups;

if (childLink.Id != null &&
if ((childLink.Ids != null && childLink.Ids.Count > 0) &&
(childLink.Values == null || childLink.Values.Count == 0) &&
childGroups != null && childGroups.Count > 0)
{
Expand Down Expand Up @@ -91,7 +91,7 @@ private static Link<TLinkAddress> TransformIndentedIdLink<TLinkAddress>(LinksGro
var groups = group.Groups;

if (groups != null && groups.Count > 0 &&
link.Id != null &&
(link.Ids != null && link.Ids.Count > 0) &&
(link.Values == null || link.Values.Count == 0))
{
var childValues = new List<Link<TLinkAddress>>();
Expand All @@ -100,7 +100,7 @@ private static Link<TLinkAddress> TransformIndentedIdLink<TLinkAddress>(LinksGro
var transformedChild = TransformIndentedIdLink(groups[i]);
childValues.Add(transformedChild);
}
return new Link<TLinkAddress>(link.Id, childValues);
return new Link<TLinkAddress>(link.Ids, childValues);
}
else if (link.Values != null && link.Values.Count == 1)
{
Expand Down
Loading
Loading