From 2d70836541533b6abababe90898b949ecb5fc8ad Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 21 May 2026 20:49:25 +0200 Subject: [PATCH 1/2] fix: phantom prefix matching and remove phantom skip cap Phantoms now correctly cover all descendants, not just direct children. The hard cap on phantom-skipped files is removed. Co-Authored-By: Claude Sonnet 4.6 --- .../Navigation/NavigationPrefixChecker.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs b/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs index 051ed12f1f..d87faa97c5 100644 --- a/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs +++ b/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs @@ -99,7 +99,6 @@ private async Task FetchAndValidateCrossLinks(IDiagnosticsCollector collector, s if (!string.IsNullOrEmpty(updateRepository) && updateReference is not null) crossLinks = crossLinkResolver.UpdateLinkReference(updateRepository, updateReference); - var skippedPhantoms = 0; foreach (var (repository, linkReference) in crossLinks.LinkReferences) { if (!_repositories.Contains(repository)) @@ -117,14 +116,9 @@ private async Task FetchAndValidateCrossLinks(IDiagnosticsCollector collector, s var path = relativeLink.Split('/').SkipLast(1); var pathUri = new Uri($"{repository}://{string.Join('/', path)}"); - var baseOfAPhantom = _phantoms.Any(p => p == pathUri); + var baseOfAPhantom = _phantoms.Any(p => IsPhantomOrDescendant(p, pathUri)); if (baseOfAPhantom) - { - skippedPhantoms++; - if (skippedPhantoms > _phantoms.Count * 3) - collector.EmitError(repository, $"Too many items are being marked as part of a phantom this looks like a bug. ({skippedPhantoms})"); continue; - } collector.EmitError(repository, $"'Can not validate '{crossLink}' it's not declared in any link reference nor is it a phantom"); continue; } @@ -157,6 +151,12 @@ private async Task FetchAndValidateCrossLinks(IDiagnosticsCollector collector, s } } + private static bool IsPhantomOrDescendant(Uri phantom, Uri candidate) => + candidate.Scheme == phantom.Scheme && + candidate.Host == phantom.Host && + (candidate.AbsolutePath == phantom.AbsolutePath || + candidate.AbsolutePath.StartsWith(phantom.AbsolutePath.TrimEnd('/') + '/')); + private async Task ReadLocalLinksJsonAsync(string localLinksJson, Cancel ctx) { try From b130a450fb58e9a50c98acf9142335af45ddbd8d Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri Benedetti Date: Thu, 21 May 2026 20:53:48 +0200 Subject: [PATCH 2/2] fix: use StringComparison.Ordinal in StartsWith (CA1310) Co-Authored-By: Claude Sonnet 4.6 --- .../Navigation/NavigationPrefixChecker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs b/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs index d87faa97c5..7bbd0f00ba 100644 --- a/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs +++ b/src/services/Elastic.Documentation.Assembler/Navigation/NavigationPrefixChecker.cs @@ -155,7 +155,7 @@ private static bool IsPhantomOrDescendant(Uri phantom, Uri candidate) => candidate.Scheme == phantom.Scheme && candidate.Host == phantom.Host && (candidate.AbsolutePath == phantom.AbsolutePath || - candidate.AbsolutePath.StartsWith(phantom.AbsolutePath.TrimEnd('/') + '/')); + candidate.AbsolutePath.StartsWith(phantom.AbsolutePath.TrimEnd('/') + '/', StringComparison.Ordinal)); private async Task ReadLocalLinksJsonAsync(string localLinksJson, Cancel ctx) {