Skip to content

Commit dce54b4

Browse files
author
Per Kops
committed
fix(providers): update MicrosoftVisualStudioThreadingAnalyzersProvider URL
- Update DocumentationLink to correct analyzer index page - Fix href link construction for relative URLs
1 parent cd48474 commit dce54b4

File tree

4 files changed

+64
-15
lines changed

4 files changed

+64
-15
lines changed

src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftVisualStudioThreadingAnalyzersProvider.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public MicrosoftVisualStudioThreadingAnalyzersProvider(
1717

1818
public static string Name => "Microsoft.VisualStudio.Threading.Analyzers";
1919

20-
public override Uri? DocumentationLink { get; set; } = new("https://github.com/microsoft/vs-threading/blob/main/doc/analyzers/index.md", UriKind.Absolute);
20+
public override Uri? DocumentationLink { get; set; } = new("https://microsoft.github.io/vs-threading/analyzers/index.html", UriKind.Absolute);
2121

2222
protected override AnalyzerProviderBaseRuleData CreateData()
2323
=> new(Name);
@@ -31,20 +31,21 @@ protected override async Task ReCollect(AnalyzerProviderBaseRuleData data)
3131
.LoadFromWebAsync(DocumentationLink!.AbsoluteUri)
3232
.ConfigureAwait(false);
3333

34-
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
35-
if (embeddedNode is not null)
34+
var tableNode = htmlDoc.DocumentNode.SelectSingleNode("//table");
35+
if (tableNode is null)
3636
{
37-
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
38-
var html = dynamicJson.GetValue("payload.blob.richText")?.ToString();
39-
40-
htmlDoc.LoadHtml(html);
37+
return;
4138
}
4239

43-
var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']")[0];
44-
var articleTableRows = articleNode
45-
.SelectNodes("//*//table[1]//tr")
40+
var articleTableRows = tableNode
41+
.SelectNodes(".//tr")?
4642
.ToList();
4743

44+
if (articleTableRows is null)
45+
{
46+
return;
47+
}
48+
4849
foreach (var row in articleTableRows)
4950
{
5051
if (row.SelectNodes("td") is null)
@@ -69,7 +70,10 @@ protected override async Task ReCollect(AnalyzerProviderBaseRuleData data)
6970

7071
var code = aHrefNode.InnerText;
7172
var title = HtmlEntity.DeEntitize(cells[TableColumnTitle].InnerText);
72-
var link = "https://github.com/" + aHrefNode.Attributes["href"].Value;
73+
var hrefValue = aHrefNode.Attributes["href"].Value;
74+
var link = hrefValue.StartsWith("http", StringComparison.OrdinalIgnoreCase)
75+
? hrefValue
76+
: $"https://microsoft.github.io/vs-threading/analyzers/{hrefValue}";
7377
var category = cells[TableColumnCategory].InnerText;
7478

7579
data.Rules.Add(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
global using System.Diagnostics.CodeAnalysis;
2+
global using Atc.CodingRules.AnalyzerProviders.Models;
13
global using Atc.CodingRules.AnalyzerProviders.Providers;
24
global using Microsoft.Extensions.Logging.Abstractions;

test/Atc.CodingRules.AnalyzerProviders.Tests/Providers/MicrosoftVisualStudioThreadingAnalyzersProviderTests.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ public sealed class MicrosoftVisualStudioThreadingAnalyzersProviderTests
1111
public async Task CollectBaseRules(
1212
ProviderCollectingMode providerCollectingMode)
1313
{
14-
// Arrange
15-
var provider = new MicrosoftVisualStudioThreadingAnalyzersProvider(NullLogger.Instance);
14+
AnalyzerProviderBaseRuleData? actual = null;
1615

17-
// Act
18-
var actual = await provider.CollectBaseRules(providerCollectingMode);
16+
await RetryHelper.ExecuteWithRetryAsync(async () =>
17+
{
18+
// Arrange
19+
var provider = new MicrosoftVisualStudioThreadingAnalyzersProvider(NullLogger.Instance);
20+
21+
// Act
22+
actual = await provider.CollectBaseRules(providerCollectingMode);
23+
});
1924

2025
// Assert
2126
Assert.NotNull(actual);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
namespace Atc.CodingRules.AnalyzerProviders.Tests;
2+
3+
/// <summary>
4+
/// Helper class for retrying flaky tests that depend on external services.
5+
/// </summary>
6+
public static class RetryHelper
7+
{
8+
/// <summary>
9+
/// Executes an async action with retry logic.
10+
/// </summary>
11+
/// <param name="action">The async action to execute.</param>
12+
/// <param name="maxRetries">Maximum number of retry attempts (default is 3).</param>
13+
/// <param name="delayBetweenRetriesMs">Delay in milliseconds between retries (default is 1000ms).</param>
14+
/// <returns>A task representing the asynchronous operation.</returns>
15+
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Retry logic needs to catch all exceptions.")]
16+
public static async Task ExecuteWithRetryAsync(
17+
Func<Task> action,
18+
int maxRetries = 3,
19+
int delayBetweenRetriesMs = 1000)
20+
{
21+
ArgumentNullException.ThrowIfNull(action);
22+
23+
var attempts = 0;
24+
while (true)
25+
{
26+
try
27+
{
28+
attempts++;
29+
await action();
30+
return;
31+
}
32+
catch when (attempts < maxRetries)
33+
{
34+
await Task.Delay(delayBetweenRetriesMs);
35+
}
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)