Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 79 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Automated dependency updates for every ecosystem in this repository.
#
# Issue #47 found each language several majors behind: eslint 9 while 10 was
# out, pytest 7 while 9 was, xunit 2 while 3 was, and four different
# `links-notation` pins. Nothing was watching, so the drift was only noticed
# when someone went looking. Dependabot opens the pull request instead.
#
# `github-actions` is included because a pinned action drifting is the same
# problem one layer down, and it is the one no language pipeline covers.
version: 2

updates:
- package-ecosystem: cargo
directory: /rust
schedule:
interval: weekly
open-pull-requests-limit: 5
commit-message:
prefix: 'chore(rust)'
groups:
# One pull request for the whole routine batch: reviewing a dozen patch
# bumps separately is how they end up ignored. Majors stay separate,
# because each needs its own reading.
rust-minor-and-patch:
update-types:
- minor
- patch

- package-ecosystem: npm
directory: /js
schedule:
interval: weekly
open-pull-requests-limit: 5
commit-message:
prefix: 'chore(js)'
groups:
js-minor-and-patch:
update-types:
- minor
- patch

- package-ecosystem: pip
directory: /python
schedule:
interval: weekly
open-pull-requests-limit: 5
commit-message:
prefix: 'chore(python)'
groups:
python-minor-and-patch:
update-types:
- minor
- patch

- package-ecosystem: nuget
directory: /csharp
schedule:
interval: weekly
open-pull-requests-limit: 5
commit-message:
prefix: 'chore(csharp)'
groups:
csharp-minor-and-patch:
update-types:
- minor
- patch

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
commit-message:
prefix: 'chore(ci)'
groups:
actions-minor-and-patch:
update-types:
- minor
- patch
12 changes: 6 additions & 6 deletions .github/workflows/csharp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

# Node powers the shared CI helper scripts under csharp/scripts.
- name: Setup Node.js
Expand Down Expand Up @@ -206,7 +206,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Restore dependencies
working-directory: ./csharp
Expand All @@ -218,7 +218,7 @@ jobs:

- name: Run tests
working-directory: ./csharp
run: dotnet test --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage"
run: dotnet test --configuration Release --no-build --coverage --coverage-output-format cobertura

- name: Run example
working-directory: ./csharp/examples
Expand Down Expand Up @@ -269,7 +269,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Restore dependencies
working-directory: ./csharp
Expand Down Expand Up @@ -355,7 +355,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Setup Node.js
uses: actions/setup-node@v7
Expand Down Expand Up @@ -498,7 +498,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Setup Node.js
uses: actions/setup-node@v7
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,25 @@ jobs:
contains(github.event.pull_request.body, '[skip-parity]'))
&& 'opted out with [skip-parity] in the pull request' || '' }}
run: node scripts/check-language-parity.mjs

# Separate job on purpose: `[skip-parity]` opts out of the source-parity gate
# above, which a legitimate single-language change sometimes needs. It must
# not also switch off the version check -- four implementations parsing with
# four different grammars is never intentional (issue #47).
links-notation-version:
name: Implementations Pin One Parser Version
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22'

- name: Check version-gate helper's own tests
run: node --test scripts/check-links-notation-parity.test.mjs

- name: Enforce one links-notation version across implementations
run: node scripts/check-links-notation-parity.mjs
4 changes: 2 additions & 2 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Autobuild
uses: github/codeql-action/autobuild@v4
Expand All @@ -82,7 +82,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

# `dotnet list package --vulnerable` exits 0 even when it finds
# advisories, so the report is inspected rather than the exit code.
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,6 @@ dmypy.json

# Cargo build output of the scratch crates under experiments/
experiments/**/target/

# jscpd duplication reports
reports/
3 changes: 2 additions & 1 deletion .gitkeep
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
# Updated: 2026-08-20T06:10:07.182Z
# Updated: 2026-08-20T07:45:09.136Z
# Updated: 2026-08-27T11:41:42.189Z
# Updated: 2026-08-27T14:37:49.393Z
# Updated: 2026-08-27T14:37:49.393Z
# Updated: 2026-08-28T13:39:51.268Z
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ This library provides universal serialization and deserialization with built-in

- **[Python](python/)** - Full implementation for Python 3.8+
- **[JavaScript](js/)** - Full implementation for Node.js 18+
- **[Rust](rust/)** - Full implementation for Rust 1.70+
- **[C#](csharp/)** - Full implementation for .NET 8.0+
- **[Rust](rust/)** - Full implementation for Rust 1.85+
- **[C#](csharp/)** - Full implementation for .NET 10.0+

All implementations share the same design philosophy and provide feature parity.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'Lino.Objects.Codec': minor
---

Update `Link.Foundation.Links.Notation` from 0.13.0 to 0.16.1 and retarget the library, tests and example from `net8.0` to `net10.0`, which that release requires. All four implementations now pin `links-notation` 0.16.1, so the conformance suites run against one grammar instead of four (issue #47).
11 changes: 11 additions & 0 deletions csharp/.changeset/20260828_140000_issue_47_xunit_v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'Lino.Objects.Codec': patch
---

Update the test tooling to current majors (issue #47).

- `xunit` 2.6.6 -> `xunit.v3` 4.0.0, `xunit.runner.visualstudio` 2.5.6 -> 4.0.0 and `Microsoft.NET.Test.Sdk` 17.8.0 -> 18.9.0.
- xunit v3 runs on Microsoft.Testing.Platform, and the .NET 10 SDK no longer supports the VSTest target that `coverlet.collector` plugs into. `global.json` opts `dotnet test` into the new platform, and coverage now comes from `Microsoft.Testing.Extensions.CodeCoverage` via `dotnet test --coverage --coverage-output-format cobertura`.
- Dropped the `!TestResults/` line from `.gitignore`: it un-ignored the directory that `[Tt]est[Rr]esult*/` two lines above deliberately ignores, so a local coverage run left the tree dirty.

All 507 tests pass unchanged; no test source needed editing.
1 change: 0 additions & 1 deletion csharp/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ packages/
*.dll
*.exe
*.pdb
!TestResults/

# User-specific files
*.rsuser
Expand Down
2 changes: 1 addition & 1 deletion csharp/examples/BasicUsage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
5 changes: 5 additions & 0 deletions csharp/global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
}
4 changes: 2 additions & 2 deletions csharp/src/Lino.Objects.Codec/Lino.Objects.Codec.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>Lino.Objects.Codec</PackageId>
Expand All @@ -22,7 +22,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Link.Foundation.Links.Notation" Version="0.13.0" />
<PackageReference Include="Link.Foundation.Links.Notation" Version="0.16.1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
<PackageReference Include="xunit.v3" Version="4.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<!-- xunit.v3 runs on Microsoft.Testing.Platform, and the .NET 10 SDK no
longer supports the VSTest target that coverlet.collector plugs into.
This is the coverage collector for the new platform; it is what
the coverage run in CI uses. -->
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.10.0" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions experiments/issue-47/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Throwaway build and run artefacts of the interop drivers.
out/
rust-interop/target/
rust-interop/Cargo.lock
csharp-interop/bin/
csharp-interop/obj/
25 changes: 25 additions & 0 deletions experiments/issue-47/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Cross-implementation round trip at `links-notation` 0.16.1

Issue #47 asks for a check that "a document written by each implementation reads
back identically in the other three" once all four pin the same parser version.

Each program here does two things:

- `write <path>` -- encode one fixed record with `encodeLine` and write it out.
- `read <dir>` -- decode every `*.lino` in the directory and print
`encodeLine(decoded)` for each.

Re-encoding is what makes the comparison language-agnostic: there is no shared
value type across Rust, JavaScript, Python and C#, but there is a shared text
format, so if every implementation reads every document to the same value, all
sixteen re-encodings are the same string.

Run it with:

```bash
./experiments/issue-47/run.sh
```

The Rust and C# drivers are separate throwaway projects that path-reference the
real packages, so nothing here changes what the crate, the npm package or the
NuGet package ship.
16 changes: 16 additions & 0 deletions experiments/issue-47/csharp-interop/Interop.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>Issue47.Interop</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\csharp\src\Lino.Objects.Codec\Lino.Objects.Codec.csproj" />
</ItemGroup>

</Project>
39 changes: 39 additions & 0 deletions experiments/issue-47/csharp-interop/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// C# side of the cross-implementation round trip (issue #47).

using Lino.Objects.Codec;

// The record every implementation writes.
static Dictionary<string, object?> Record() => new()
{
["phase"] = "stream_end",
["bytes"] = 2827L,
["complete"] = true,
["server"] = new Dictionary<string, object?>
{
["host"] = "127.0.0.1",
["port"] = 18878L,
},
["models"] = new List<object?> { "claude-haiku", "claude-opus" },
};

var mode = args[0];
var target = args[1];

if (mode == "write")
{
File.WriteAllText(target, Readable.EncodeLine(Record()) + "\n");
}
else if (mode == "read")
{
foreach (var path in Directory.GetFiles(target, "*.lino").OrderBy(p => p, StringComparer.Ordinal))
{
var notation = File.ReadAllText(path).Trim();
var value = Readable.DecodeLine(notation);
Console.WriteLine($"csharp reading {Path.GetFileName(path)}: {Readable.EncodeLine(value)}");
}
}
else
{
Console.Error.WriteLine("usage: Interop write <path> | read <dir>");
Environment.Exit(2);
}
Loading
Loading