From 8104b9e9a3abac3e3dcafecfe0c98a16b301cf40 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Tue, 8 Jul 2025 09:26:19 -0500
Subject: [PATCH 1/8] Initial commit of project structure.
---
dotnet/.gitignore | 176 +++++++++++++++++++++++
dotnet/Query/Query.sln | 31 ++++
dotnet/Query/Query/Query.csproj | 7 +
dotnet/Query/QueryTest/MSTestSettings.cs | 1 +
dotnet/Query/QueryTest/QueryTest.csproj | 29 ++++
5 files changed, 244 insertions(+)
create mode 100644 dotnet/.gitignore
create mode 100644 dotnet/Query/Query.sln
create mode 100644 dotnet/Query/Query/Query.csproj
create mode 100644 dotnet/Query/QueryTest/MSTestSettings.cs
create mode 100644 dotnet/Query/QueryTest/QueryTest.csproj
diff --git a/dotnet/.gitignore b/dotnet/.gitignore
new file mode 100644
index 00000000..91fa9507
--- /dev/null
+++ b/dotnet/.gitignore
@@ -0,0 +1,176 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+*.ide
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Oo]bj/
+*/**/bin
+
+# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
+!packages/*/build/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+#packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+
+#LightSwitch generated files
+GeneratedArtifacts/
+_Pvt_Extensions/
+ModelManifest.xml
+
+# =========================
+# Windows detritus
+# =========================
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac desktop service store files
+.DS_Store
+
+packages/
+acceptance/
+output/
+.built
+.compared
+.sln_built_debug
+*.userprefs
+*.nupkg
+Gherkin.NuGetPackages/bin/
+.build*
+.built*
+.vscode
+.run_tests
+.generated
+.packed
+.tested
+.fixprotoc
+.vs/
diff --git a/dotnet/Query/Query.sln b/dotnet/Query/Query.sln
new file mode 100644
index 00000000..59ccba02
--- /dev/null
+++ b/dotnet/Query/Query.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36221.1 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Query", "Query\Query.csproj", "{01EF081E-A17A-4630-9C7D-40BA4BE3F9BC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueryTest", "QueryTest\QueryTest.csproj", "{F0EA5832-C5B7-42CF-9BDB-6EE21C589C8B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {01EF081E-A17A-4630-9C7D-40BA4BE3F9BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01EF081E-A17A-4630-9C7D-40BA4BE3F9BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01EF081E-A17A-4630-9C7D-40BA4BE3F9BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01EF081E-A17A-4630-9C7D-40BA4BE3F9BC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0EA5832-C5B7-42CF-9BDB-6EE21C589C8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0EA5832-C5B7-42CF-9BDB-6EE21C589C8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0EA5832-C5B7-42CF-9BDB-6EE21C589C8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0EA5832-C5B7-42CF-9BDB-6EE21C589C8B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {923928DC-6FB0-4C25-9B2B-721A9B60C602}
+ EndGlobalSection
+EndGlobal
diff --git a/dotnet/Query/Query/Query.csproj b/dotnet/Query/Query/Query.csproj
new file mode 100644
index 00000000..dbdcea46
--- /dev/null
+++ b/dotnet/Query/Query/Query.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/dotnet/Query/QueryTest/MSTestSettings.cs b/dotnet/Query/QueryTest/MSTestSettings.cs
new file mode 100644
index 00000000..aaf278c8
--- /dev/null
+++ b/dotnet/Query/QueryTest/MSTestSettings.cs
@@ -0,0 +1 @@
+[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
diff --git a/dotnet/Query/QueryTest/QueryTest.csproj b/dotnet/Query/QueryTest/QueryTest.csproj
new file mode 100644
index 00000000..0138d060
--- /dev/null
+++ b/dotnet/Query/QueryTest/QueryTest.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ latest
+ enable
+ enable
+ true
+ Exe
+ true
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 8a7a7e9e14ed932af67ea1169396d5a712b2c4bc Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Tue, 8 Jul 2025 09:47:12 -0500
Subject: [PATCH 2/8] first class ported
---
dotnet/Query/Query/Lineage.cs | 99 +++++++++++++++++++++++++++
dotnet/Query/Query/PortingTemplate.cs | 8 +++
dotnet/Query/Query/Query.csproj | 6 ++
3 files changed, 113 insertions(+)
create mode 100644 dotnet/Query/Query/Lineage.cs
create mode 100644 dotnet/Query/Query/PortingTemplate.cs
diff --git a/dotnet/Query/Query/Lineage.cs b/dotnet/Query/Query/Lineage.cs
new file mode 100644
index 00000000..7d0a7eca
--- /dev/null
+++ b/dotnet/Query/Query/Lineage.cs
@@ -0,0 +1,99 @@
+using System.ComponentModel.DataAnnotations;
+using System;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query;
+
+///
+/// A structure containing all ancestors of a given
+/// element or .
+///
+///
+/// See .
+///
+public sealed class Lineage
+{
+ private readonly GherkinDocument _document;
+ private readonly Feature? _feature;
+ private readonly Rule? _rule;
+ private readonly Scenario? _scenario;
+ private readonly Examples? _examples;
+ private readonly int? _examplesIndex;
+ private readonly TableRow? _example;
+ private readonly int? _exampleIndex;
+
+ internal Lineage([Required] GherkinDocument document)
+ {
+ _document = document ?? throw new ArgumentNullException(nameof(document));
+ _feature = null;
+ _rule = null;
+ _scenario = null;
+ _examples = null;
+ _examplesIndex = null;
+ _example = null;
+ _exampleIndex = null;
+ }
+
+ internal Lineage(Lineage parent, Feature feature)
+ : this(parent._document, feature, null, null, null, null, null, null) { }
+
+ internal Lineage(Lineage parent, Rule rule)
+ : this(parent._document, parent._feature, rule, null, null, null, null, null) { }
+
+ internal Lineage(Lineage parent, Scenario scenario)
+ : this(parent._document, parent._feature, parent._rule, scenario, null, null, null, null) { }
+
+ internal Lineage(Lineage parent, Examples examples, int examplesIndex)
+ : this(parent._document, parent._feature, parent._rule, parent._scenario, examples, examplesIndex, null, null) { }
+
+ internal Lineage(Lineage parent, TableRow example, int exampleIndex)
+ : this(parent._document, parent._feature, parent._rule, parent._scenario, parent._examples, parent._examplesIndex, example, exampleIndex) { }
+
+ private Lineage(
+ [Required] GherkinDocument document,
+ Feature? feature,
+ Rule? rule,
+ Scenario? scenario,
+ Examples? examples,
+ int? examplesIndex,
+ TableRow? example,
+ int? exampleIndex)
+ {
+ _document = document ?? throw new ArgumentNullException(nameof(document));
+ _feature = feature;
+ _rule = rule;
+ _scenario = scenario;
+ _examples = examples;
+ _examplesIndex = examplesIndex;
+ _example = example;
+ _exampleIndex = exampleIndex;
+ }
+
+ public GherkinDocument Document => _document;
+ public Feature? Feature => _feature;
+ public Rule? Rule => _rule;
+ public Scenario? Scenario => _scenario;
+ public Examples? Examples => _examples;
+ public TableRow? Example => _example;
+ public int? ExamplesIndex => _examplesIndex;
+ public int? ExampleIndex => _exampleIndex;
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj is not Lineage other) return false;
+ return Equals(_document, other._document)
+ && Equals(_feature, other._feature)
+ && Equals(_rule, other._rule)
+ && Equals(_scenario, other._scenario)
+ && Equals(_examples, other._examples)
+ && Equals(_example, other._example)
+ && Equals(_examplesIndex, other._examplesIndex)
+ && Equals(_exampleIndex, other._exampleIndex);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(_document, _feature, _rule, _scenario, _examples, _example, _examplesIndex, _exampleIndex);
+ }
+}
diff --git a/dotnet/Query/Query/PortingTemplate.cs b/dotnet/Query/Query/PortingTemplate.cs
new file mode 100644
index 00000000..c037ba10
--- /dev/null
+++ b/dotnet/Query/Query/PortingTemplate.cs
@@ -0,0 +1,8 @@
+// File-scoped namespace for all ported classes
+namespace Io.Cucumber.Query;
+
+// ...existing code...
+// This file will be used as a template for porting Java classes to C#
+// Each Java class will be ported as a public C# class in this namespace
+// Nullable types and [Required] attributes will be used as specified
+// Implementation will follow idiomatic C# and .NET Standard 2.0 compatibility
diff --git a/dotnet/Query/Query/Query.csproj b/dotnet/Query/Query/Query.csproj
index dbdcea46..e05b0404 100644
--- a/dotnet/Query/Query/Query.csproj
+++ b/dotnet/Query/Query/Query.csproj
@@ -2,6 +2,12 @@
netstandard2.0
+ latest
+
+
+
+
+
From 281e078cd7327aceb0f9c30e48d4b14d66130601 Mon Sep 17 00:00:00 2001
From: Chris Rudolphi <1702962+clrudolphi@users.noreply.github.com>
Date: Tue, 8 Jul 2025 14:44:53 -0500
Subject: [PATCH 3/8] Implementation plus tests
---
dotnet/Query/Query/Lineage.cs | 17 +-
dotnet/Query/Query/LineageReducer.cs | 25 +
dotnet/Query/Query/LineageReducerAscending.cs | 47 ++
.../Query/Query/LineageReducerDescending.cs | 47 ++
dotnet/Query/Query/Lineages.cs | 105 ++++
dotnet/Query/Query/NamingCollector.cs | 109 ++++
dotnet/Query/Query/NamingStrategy.cs | 93 +++
dotnet/Query/Query/PortingTemplate.cs | 5 +-
dotnet/Query/Query/Query.cs | 565 ++++++++++++++++++
dotnet/Query/Query/Query.csproj | 4 +
dotnet/Query/Query/TimestampComparer.cs | 30 +
dotnet/Query/QueryTest/QueryAcceptanceTest.cs | 199 ++++++
dotnet/Query/QueryTest/QueryTest.cs | 64 ++
dotnet/Query/QueryTest/QueryTest.csproj | 10 +-
.../Resources/attachments.feature.ndjson | 77 +++
.../attachments.feature.query-results.json | 407 +++++++++++++
.../QueryTest/Resources/cdata.feature.ndjson | 12 +
.../Resources/data-tables.feature.ndjson | 15 +
.../QueryTest/Resources/empty.feature.ndjson | 9 +
.../empty.feature.query-results.json | 91 +++
.../examples-tables-attachment.feature.ndjson | 21 +
.../Resources/examples-tables.feature.ndjson | 100 ++++
...examples-tables.feature.query-results.json | 513 ++++++++++++++++
.../Resources/hooks-attachment.feature.ndjson | 20 +
.../hooks-conditional.feature.ndjson | 36 ++
.../Resources/hooks-named.feature.ndjson | 18 +
.../QueryTest/Resources/hooks.feature.ndjson | 39 ++
.../hooks.feature.query-results.json | 221 +++++++
.../Resources/markdown.feature.md.ndjson | 35 ++
.../Resources/minimal.feature.ndjson | 12 +
.../minimal.feature.query-results.json | 111 ++++
.../Resources/parameter-types.feature.ndjson | 13 +
.../Resources/pending.feature.ndjson | 30 +
.../QueryTest/Resources/retry.feature.ndjson | 59 ++
.../QueryTest/Resources/rules.feature.ndjson | 47 ++
.../rules.feature.query-results.json | 252 ++++++++
.../Resources/skipped.feature.ndjson | 33 +
.../Resources/stack-traces.feature.ndjson | 12 +
.../Resources/undefined.feature.ndjson | 29 +
.../unknown-parameter-type.feature.ndjson | 12 +
.../Query/QueryTest/TimestampComparerTest.cs | 46 ++
41 files changed, 3576 insertions(+), 14 deletions(-)
create mode 100644 dotnet/Query/Query/LineageReducer.cs
create mode 100644 dotnet/Query/Query/LineageReducerAscending.cs
create mode 100644 dotnet/Query/Query/LineageReducerDescending.cs
create mode 100644 dotnet/Query/Query/Lineages.cs
create mode 100644 dotnet/Query/Query/NamingCollector.cs
create mode 100644 dotnet/Query/Query/NamingStrategy.cs
create mode 100644 dotnet/Query/Query/Query.cs
create mode 100644 dotnet/Query/Query/TimestampComparer.cs
create mode 100644 dotnet/Query/QueryTest/QueryAcceptanceTest.cs
create mode 100644 dotnet/Query/QueryTest/QueryTest.cs
create mode 100644 dotnet/Query/QueryTest/Resources/attachments.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/attachments.feature.query-results.json
create mode 100644 dotnet/Query/QueryTest/Resources/cdata.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/data-tables.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/empty.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/empty.feature.query-results.json
create mode 100644 dotnet/Query/QueryTest/Resources/examples-tables-attachment.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/examples-tables.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/examples-tables.feature.query-results.json
create mode 100644 dotnet/Query/QueryTest/Resources/hooks-attachment.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/hooks-conditional.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/hooks-named.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/hooks.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/hooks.feature.query-results.json
create mode 100644 dotnet/Query/QueryTest/Resources/markdown.feature.md.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/minimal.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/minimal.feature.query-results.json
create mode 100644 dotnet/Query/QueryTest/Resources/parameter-types.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/pending.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/retry.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/rules.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/rules.feature.query-results.json
create mode 100644 dotnet/Query/QueryTest/Resources/skipped.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/stack-traces.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/undefined.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/Resources/unknown-parameter-type.feature.ndjson
create mode 100644 dotnet/Query/QueryTest/TimestampComparerTest.cs
diff --git a/dotnet/Query/Query/Lineage.cs b/dotnet/Query/Query/Lineage.cs
index 7d0a7eca..e40f8822 100644
--- a/dotnet/Query/Query/Lineage.cs
+++ b/dotnet/Query/Query/Lineage.cs
@@ -1,3 +1,4 @@
+#nullable enable
using System.ComponentModel.DataAnnotations;
using System;
using Io.Cucumber.Messages.Types;
@@ -22,17 +23,7 @@ public sealed class Lineage
private readonly TableRow? _example;
private readonly int? _exampleIndex;
- internal Lineage([Required] GherkinDocument document)
- {
- _document = document ?? throw new ArgumentNullException(nameof(document));
- _feature = null;
- _rule = null;
- _scenario = null;
- _examples = null;
- _examplesIndex = null;
- _example = null;
- _exampleIndex = null;
- }
+ internal Lineage([Required] GherkinDocument document) : this(document, null, null, null, null, null, null, null) { }
internal Lineage(Lineage parent, Feature feature)
: this(parent._document, feature, null, null, null, null, null, null) { }
@@ -92,8 +83,10 @@ public override bool Equals(object? obj)
&& Equals(_exampleIndex, other._exampleIndex);
}
+ // Rest of the code remains unchanged
+
public override int GetHashCode()
{
- return HashCode.Combine(_document, _feature, _rule, _scenario, _examples, _example, _examplesIndex, _exampleIndex);
+ return (_document, _feature, _rule, _scenario, _examples, _example, _examplesIndex, _exampleIndex).GetHashCode();
}
}
diff --git a/dotnet/Query/Query/LineageReducer.cs b/dotnet/Query/Query/LineageReducer.cs
new file mode 100644
index 00000000..39848491
--- /dev/null
+++ b/dotnet/Query/Query/LineageReducer.cs
@@ -0,0 +1,25 @@
+using System;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query
+{
+ // port of io.cucumber.query.LineageReducer (Java)
+ public interface ILineageReducer
+ {
+ T Reduce(Lineage lineage);
+ T Reduce(Lineage lineage, Pickle pickle);
+ }
+
+ // port of io.cucumber.query.LineageReducer.Collector (Java)
+ public interface ICollector
+ {
+ void Add(GherkinDocument document);
+ void Add(Feature feature);
+ void Add(Rule rule);
+ void Add(Scenario scenario);
+ void Add(Examples examples, int index);
+ void Add(TableRow example, int index);
+ void Add(Pickle pickle);
+ T Finish();
+ }
+}
diff --git a/dotnet/Query/Query/LineageReducerAscending.cs b/dotnet/Query/Query/LineageReducerAscending.cs
new file mode 100644
index 00000000..b3c9f10b
--- /dev/null
+++ b/dotnet/Query/Query/LineageReducerAscending.cs
@@ -0,0 +1,47 @@
+using System;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query
+{
+ // Faithful port of io.cucumber.query.LineageReducerAscending (Java)
+ public class LineageReducerAscending : ILineageReducer
+ {
+ private readonly Func> _collectorSupplier;
+
+ public LineageReducerAscending(Func> collectorSupplier)
+ {
+ _collectorSupplier = collectorSupplier ?? throw new ArgumentNullException(nameof(collectorSupplier));
+ }
+
+ public T Reduce(Lineage lineage)
+ {
+ var collector = _collectorSupplier();
+ ReduceAddLineage(collector, lineage);
+ return collector.Finish();
+ }
+
+ public T Reduce(Lineage lineage, Pickle pickle)
+ {
+ var collector = _collectorSupplier();
+ collector.Add(pickle);
+ ReduceAddLineage(collector, lineage);
+ return collector.Finish();
+ }
+
+ private static void ReduceAddLineage(ICollector collector, Lineage lineage)
+ {
+ if (lineage.Example != null)
+ collector.Add(lineage.Example, lineage.ExampleIndex ?? 0);
+ if (lineage.Examples != null)
+ collector.Add(lineage.Examples, lineage.ExamplesIndex ?? 0);
+ if (lineage.Scenario != null)
+ collector.Add(lineage.Scenario);
+ if (lineage.Rule != null)
+ collector.Add(lineage.Rule);
+ if (lineage.Feature != null)
+ collector.Add(lineage.Feature);
+ if (lineage.Document != null)
+ collector.Add(lineage.Document);
+ }
+ }
+}
diff --git a/dotnet/Query/Query/LineageReducerDescending.cs b/dotnet/Query/Query/LineageReducerDescending.cs
new file mode 100644
index 00000000..a525458e
--- /dev/null
+++ b/dotnet/Query/Query/LineageReducerDescending.cs
@@ -0,0 +1,47 @@
+using System;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query
+{
+ // Faithful port of io.cucumber.query.LineageReducerDescending (Java)
+ public class LineageReducerDescending : ILineageReducer
+ {
+ private readonly Func> _collectorSupplier;
+
+ public LineageReducerDescending(Func> collectorSupplier)
+ {
+ _collectorSupplier = collectorSupplier ?? throw new ArgumentNullException(nameof(collectorSupplier));
+ }
+
+ public T Reduce(Lineage lineage)
+ {
+ var collector = _collectorSupplier();
+ ReduceAddLineage(collector, lineage);
+ return collector.Finish();
+ }
+
+ public T Reduce(Lineage lineage, Pickle pickle)
+ {
+ var collector = _collectorSupplier();
+ ReduceAddLineage(collector, lineage);
+ collector.Add(pickle);
+ return collector.Finish();
+ }
+
+ private static void ReduceAddLineage(ICollector collector, Lineage lineage)
+ {
+ if (lineage.Document != null)
+ collector.Add(lineage.Document);
+ if (lineage.Feature != null)
+ collector.Add(lineage.Feature);
+ if (lineage.Rule != null)
+ collector.Add(lineage.Rule);
+ if (lineage.Scenario != null)
+ collector.Add(lineage.Scenario);
+ if (lineage.Examples != null)
+ collector.Add(lineage.Examples, lineage.ExamplesIndex ?? 0);
+ if (lineage.Example != null)
+ collector.Add(lineage.Example, lineage.ExampleIndex ?? 0);
+ }
+ }
+}
diff --git a/dotnet/Query/Query/Lineages.cs b/dotnet/Query/Query/Lineages.cs
new file mode 100644
index 00000000..37611579
--- /dev/null
+++ b/dotnet/Query/Query/Lineages.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query
+{
+ // port of io.cucumber.query.Lineages (Java)
+ internal static class Lineages
+ {
+ ///
+ /// Create map of a GherkinDocument element to its Lineage in that document.
+ ///
+ /// The GherkinDocument to create the lineage of.
+ /// A map of the document elements to their lineage.
+ public static Dictionary Of(GherkinDocument document)
+ {
+ var elements = new Dictionary();
+ var lineage = new Lineage(document);
+ var uri = document.Uri ?? throw new ArgumentException("document.uri must not be null");
+ elements[uri] = lineage;
+ if (document.Feature != null)
+ OfFeature(lineage, elements)(document.Feature);
+ return elements;
+ }
+
+ private static Action OfFeature(Lineage parent, Dictionary elements)
+ {
+ return feature =>
+ {
+ var lineage = new Lineage(parent, feature);
+ foreach (var child in feature.Children)
+ OfFeatureChild(lineage, elements)(child);
+ };
+ }
+
+ private static Action OfFeatureChild(Lineage parent, Dictionary elements)
+ {
+ return featureChild =>
+ {
+ if (featureChild.Scenario != null)
+ OfScenario(parent, elements)(featureChild.Scenario);
+ if (featureChild.Rule != null)
+ OfRule(parent, elements)(featureChild.Rule);
+ };
+ }
+
+ private static Action OfRule(Lineage parent, Dictionary elements)
+ {
+ return rule =>
+ {
+ var lineage = new Lineage(parent, rule);
+ elements[rule.Id] = lineage;
+ foreach (var child in rule.Children)
+ OfRuleChild(lineage, elements)(child);
+ };
+ }
+
+ private static Action OfRuleChild(Lineage parent, Dictionary elements)
+ {
+ return ruleChild =>
+ {
+ if (ruleChild.Scenario != null)
+ OfScenario(parent, elements)(ruleChild.Scenario);
+ };
+ }
+
+ private static Action OfScenario(Lineage parent, Dictionary elements)
+ {
+ return scenario =>
+ {
+ var lineage = new Lineage(parent, scenario);
+ elements[scenario.Id] = lineage;
+ ForEachIndexed(scenario.Examples, OfExamples(lineage, elements));
+ };
+ }
+
+ private static Action OfExamples(Lineage parent, Dictionary elements)
+ {
+ return (examples, examplesIndex) =>
+ {
+ var lineage = new Lineage(parent, examples, examplesIndex);
+ elements[examples.Id] = lineage;
+ ForEachIndexed(examples.TableBody, OfExample(lineage, elements));
+ };
+ }
+
+ private static Action OfExample(Lineage parent, Dictionary elements)
+ {
+ return (example, exampleIndex) =>
+ {
+ var lineage = new Lineage(parent, example, exampleIndex);
+ elements[example.Id] = lineage;
+ };
+ }
+
+ private static void ForEachIndexed(IList items, Action consumer)
+ {
+ if (items == null) return;
+ for (int i = 0; i < items.Count; i++)
+ {
+ consumer(items[i], i);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/Query/Query/NamingCollector.cs b/dotnet/Query/Query/NamingCollector.cs
new file mode 100644
index 00000000..6d518123
--- /dev/null
+++ b/dotnet/Query/Query/NamingCollector.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query
+{
+ ///
+ /// Names GherkinDocument elements or Pickles.
+ ///
+ internal class NamingCollector : ICollector
+ {
+ // There are at most 5 levels to a feature file.
+ private readonly List parts = new List(5);
+ private readonly string delimiter = " - ";
+ private readonly Strategy strategy;
+ private readonly FeatureName featureName;
+ private readonly ExampleName exampleName;
+
+ private string scenarioName;
+ private bool isExample;
+ private int examplesIndex;
+
+ public static Func Of(Strategy strategy, FeatureName featureName, ExampleName exampleName)
+ {
+ return () => new NamingCollector(strategy, featureName, exampleName);
+ }
+
+ public NamingCollector(Strategy strategy, FeatureName featureName, ExampleName exampleName)
+ {
+ this.strategy = strategy;
+ this.featureName = featureName;
+ this.exampleName = exampleName;
+ }
+
+ public void Add(GherkinDocument document) { }
+ public void Add(Feature feature)
+ {
+ if (featureName == FeatureName.INCLUDE || strategy == Strategy.SHORT)
+ {
+ parts.Add(feature.Name);
+ }
+ }
+ public void Add(Rule rule)
+ {
+ parts.Add(rule.Name);
+ }
+ public void Add(Scenario scenario)
+ {
+ scenarioName = scenario.Name;
+ parts.Add(scenarioName);
+ }
+ public void Add(Examples examples, int index)
+ {
+ parts.Add(examples.Name);
+ this.examplesIndex = index;
+ }
+ public void Add(TableRow example, int index)
+ {
+ isExample = true;
+ parts.Add("#" + (examplesIndex + 1) + "." + (index + 1));
+ }
+ public void Add(Pickle pickle)
+ {
+ string pickleName = pickle.Name;
+
+ // Case 0: Pickles with an empty lineage
+ if (scenarioName == null)
+ {
+ parts.Add(pickleName);
+ return;
+ }
+
+ // Case 1: Pickles from a scenario outline
+ if (isExample)
+ {
+ switch (exampleName)
+ {
+ case ExampleName.NUMBER:
+ break;
+ case ExampleName.NUMBER_AND_PICKLE_IF_PARAMETERIZED:
+ bool parameterized = !scenarioName.Equals(pickleName);
+ if (parameterized)
+ {
+ string exampleNumber = parts[parts.Count - 1];
+ parts.RemoveAt(parts.Count - 1);
+ parts.Add(exampleNumber + ": " + pickleName);
+ }
+ break;
+ case ExampleName.PICKLE:
+ parts.RemoveAt(parts.Count - 1); // Remove example number
+ parts.Add(pickleName);
+ break;
+ }
+ }
+ // Case 2: Pickles from a scenario
+ // Nothing to do, scenario name and pickle name are the same.
+ }
+
+ public string Finish()
+ {
+ if (strategy == Strategy.SHORT)
+ {
+ return parts.Count > 0 ? parts[parts.Count - 1] : string.Empty;
+ }
+ return string.Join(delimiter, parts.Where(s => !string.IsNullOrEmpty(s)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/Query/Query/NamingStrategy.cs b/dotnet/Query/Query/NamingStrategy.cs
new file mode 100644
index 00000000..73af2f03
--- /dev/null
+++ b/dotnet/Query/Query/NamingStrategy.cs
@@ -0,0 +1,93 @@
+using System;
+using Io.Cucumber.Messages.Types;
+
+namespace Io.Cucumber.Query
+{
+ ///
+ /// Names Pickles and other elements in a GherkinDocument.
+ ///
+ public enum Strategy
+ {
+ LONG,
+ SHORT
+ }
+
+ public enum ExampleName
+ {
+ NUMBER,
+ PICKLE,
+ NUMBER_AND_PICKLE_IF_PARAMETERIZED
+ }
+
+ public enum FeatureName
+ {
+ INCLUDE,
+ EXCLUDE
+ }
+ public abstract class NamingStrategy : ILineageReducer
+ {
+
+ public static Builder Create(Strategy strategy)
+ {
+ return new Builder(strategy);
+ }
+
+ protected NamingStrategy() { }
+
+ public abstract string Reduce(Lineage lineage);
+ public abstract string Reduce(Lineage lineage, Pickle pickle);
+
+ public class Builder
+ {
+ private readonly Strategy _strategy;
+ private FeatureName _featureName = Io.Cucumber.Query.FeatureName.INCLUDE;
+ private ExampleName _exampleName = Io.Cucumber.Query.ExampleName.NUMBER_AND_PICKLE_IF_PARAMETERIZED;
+
+ public Builder(Strategy strategy)
+ {
+ _strategy = strategy;
+ }
+
+ public Builder ExampleName(ExampleName exampleName)
+ {
+ _exampleName = exampleName;
+ return this;
+ }
+
+ public Builder FeatureName(FeatureName featureName)
+ {
+ _featureName = featureName;
+ return this;
+ }
+
+ public NamingStrategy Build()
+ {
+ return new Adaptor(
+ new LineageReducerDescending(
+ () => new NamingCollector((Strategy)_strategy, (FeatureName)_featureName, (ExampleName)_exampleName)
+ )
+ );
+ }
+ }
+
+ private class Adaptor : NamingStrategy
+ {
+ private readonly ILineageReducer _delegate;
+
+ public Adaptor(ILineageReducer del)
+ {
+ _delegate = del;
+ }
+
+ public override string Reduce(Lineage lineage)
+ {
+ return _delegate.Reduce(lineage);
+ }
+
+ public override string Reduce(Lineage lineage, Pickle pickle)
+ {
+ return _delegate.Reduce(lineage, pickle);
+ }
+ }
+ }
+}
diff --git a/dotnet/Query/Query/PortingTemplate.cs b/dotnet/Query/Query/PortingTemplate.cs
index c037ba10..8d9aa534 100644
--- a/dotnet/Query/Query/PortingTemplate.cs
+++ b/dotnet/Query/Query/PortingTemplate.cs
@@ -1,7 +1,10 @@
// File-scoped namespace for all ported classes
namespace Io.Cucumber.Query;
-// ...existing code...
+using System;
+using System.Collections.Generic;
+using Io.Cucumber.Messages.Types;
+
// This file will be used as a template for porting Java classes to C#
// Each Java class will be ported as a public C# class in this namespace
// Nullable types and [Required] attributes will be used as specified
diff --git a/dotnet/Query/Query/Query.cs b/dotnet/Query/Query/Query.cs
new file mode 100644
index 00000000..7210cfaa
--- /dev/null
+++ b/dotnet/Query/Query/Query.cs
@@ -0,0 +1,565 @@
+#nullable enable
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Collections.Generic;
+using System.Linq;
+using Io.Cucumber.Messages.Types;
+using System.Collections.Concurrent;
+
+namespace Io.Cucumber.Query;
+
+// Ported from io.cucumber.query.Query (Java)
+public class Query
+{
+ // Internal state for queries
+ private readonly List _gherkinDocuments = new();
+ private readonly List _pickles = new();
+ private readonly List _testCases = new();
+ private readonly List _testStepFinished = new();
+ private readonly List _testCaseFinished = new();
+ private readonly List _testCaseStarted = new();
+
+ // Additional internal state for queries (to be expanded as needed)
+ private readonly ConcurrentDictionary _testCaseStartedById = new();
+ private readonly ConcurrentDictionary _testCaseFinishedByTestCaseStartedId = new();
+ private readonly ConcurrentDictionary> _testStepsFinishedByTestCaseStartedId = new();
+ private readonly ConcurrentDictionary> _testStepsStartedByTestCaseStartedId = new();
+ private readonly ConcurrentDictionary _pickleById = new();
+ private readonly ConcurrentDictionary _testCaseById = new();
+ private readonly ConcurrentDictionary _stepById = new();
+ private readonly ConcurrentDictionary _testStepById = new();
+ private readonly ConcurrentDictionary _pickleStepById = new();
+ private readonly ConcurrentDictionary _hookById = new();
+ private readonly ConcurrentDictionary> _attachmentsByTestCaseStartedId = new();
+ private readonly ConcurrentDictionary