Skip to content

Commit

Permalink
Generate unique link target (#973)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt authored Oct 29, 2022
1 parent 2a8f3da commit 967440f
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/Documentation/DocumentationGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ private IEnumerable<DocumentationGeneratorResult> GenerateMembers(TypeDocumentat

foreach (ISymbol overloadSymbol in grouping.OrderBy(f => f.ToDisplayString(format, additionalOptions)))
{
string id = DocumentationUrlProvider.GetFragment(overloadSymbol);
string id = UrlProvider.GetFragment(overloadSymbol);

writer.WriteLinkTarget(id);
writer.WriteStartHeading(2);
Expand Down
2 changes: 1 addition & 1 deletion src/Documentation/DocumentationWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1961,7 +1961,7 @@ string GetLocalLinkTarget()
if (en.MoveNext()
&& en.MoveNext())
{
return DocumentationUrlProvider.GetFragment(symbol);
return UrlProvider.GetFragment(symbol);
}
}
}
Expand Down
65 changes: 60 additions & 5 deletions src/Documentation/UrlProviders/DocumentationUrlProvider.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Roslynator.Text;

namespace Roslynator.Documentation
{
public abstract class DocumentationUrlProvider
{
private static readonly Regex _notWordCharOrUnderscoreRegex = new(@"[^\w_]");
private readonly Dictionary<string, string> _symbolToLinkMap = new();

protected DocumentationUrlProvider(UrlSegmentProvider segmentProvider, IEnumerable<ExternalUrlProvider> externalProviders = null)
{
Expand Down Expand Up @@ -115,13 +117,66 @@ internal string GetUrlToRoot(int depth, char separator, bool scrollToContent = f
return StringBuilderCache.GetStringAndFree(sb);
}

internal static string GetFragment(ISymbol symbol)
internal string GetFragment(ISymbol symbol)
{
string id = symbol.GetDocumentationCommentId();

id = TextUtility.RemovePrefixFromDocumentationCommentId(id);
if (!_symbolToLinkMap.TryGetValue(id, out string link))
{
int hashCode = GetDeterministicHashCode(id);

long linkCode;
if (hashCode >= 0)
{
linkCode = (uint)hashCode;
}
else
{
linkCode = int.MaxValue + (uint)Math.Abs(hashCode);
}

link = linkCode.ToString(CultureInfo.InvariantCulture);

if (_symbolToLinkMap.ContainsValue(link))
{
Debug.Fail(id);

linkCode += uint.MaxValue;
link = linkCode.ToString(CultureInfo.InvariantCulture);

while (_symbolToLinkMap.ContainsValue(link))
{
linkCode++;
link = linkCode.ToString(CultureInfo.InvariantCulture);
}
}

_symbolToLinkMap.Add(id, link);
}

return link;
}

return _notWordCharOrUnderscoreRegex.Replace(id, "_");
// https://andrewlock.net/why-is-string-gethashcode-different-each-time-i-run-my-program-in-net-core/#a-deterministic-gethashcode-implementation
private static int GetDeterministicHashCode(string s)
{
unchecked
{
int hash1 = (5381 << 16) + 5381;
int hash2 = hash1;

for (int i = 0; i < s.Length; i += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ s[i];

if (i == s.Length - 1)
break;

hash2 = ((hash2 << 5) + hash2) ^ s[i + 1];
}

return hash1 + (hash2 * 1566083941);
}
}
}
}

0 comments on commit 967440f

Please sign in to comment.