Skip to content

Commit e0cb5f2

Browse files
authored
refactor AVM module completion to include module status in documentation
1 parent 4705df8 commit e0cb5f2

3 files changed

Lines changed: 98 additions & 18 deletions

File tree

‎src/Bicep.LangServer.UnitTests/Completions/ModuleReferenceCompletionProviderTests.cs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,10 @@ public async Task GetFilteredCompletions_WithPublicAvmModulePathCompletions_Uses
581581
avmDisplayNameProviderMock
582582
.Setup(x => x.TryGetModuleDisplayName("bicep/avm/ptn/ai-platform/baseline", out moduleDisplayName))
583583
.Returns(true);
584+
string? moduleStatus = "Available";
585+
avmDisplayNameProviderMock
586+
.Setup(x => x.TryGetModuleStatus("bicep/avm/ptn/ai-platform/baseline", out moduleStatus))
587+
.Returns(true);
584588

585589
var (completionContext, sourceFile) = GetBicepCompletionContext("module test 'br/public:avm/ptn/ai-platform/|'");
586590
var moduleReferenceCompletionProvider = new ModuleReferenceCompletionProvider(
@@ -599,6 +603,7 @@ public async Task GetFilteredCompletions_WithPublicAvmModulePathCompletions_Uses
599603
x.Documentation!.MarkupContent!.Value.Should().NotContain("**Display name:**");
600604
x.Documentation.MarkupContent.Value.Should().Contain("### AI platform baseline");
601605
x.Documentation.MarkupContent.Value.Should().Contain("**Full module path:** avm/ptn/ai-platform/baseline");
606+
x.Documentation.MarkupContent.Value.Should().Contain("**Status:** \uD83D\uDFE2 Available");
602607
x.Documentation.MarkupContent.Value.Should().Contain("**Description:** module description");
603608
});
604609

‎src/Bicep.LangServer/Completions/AvmModuleDisplayNameProvider.cs‎

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public interface IAvmModuleDisplayNameProvider
1414
void StartCache();
1515

1616
bool TryGetModuleDisplayName(string modulePath, [NotNullWhen(true)] out string? displayName);
17+
18+
bool TryGetModuleStatus(string modulePath, [NotNullWhen(true)] out string? moduleStatus);
1719
}
1820

1921
public class AvmModuleDisplayNameProvider : IAvmModuleDisplayNameProvider
@@ -22,12 +24,12 @@ public class AvmModuleDisplayNameProvider : IAvmModuleDisplayNameProvider
2224
private static readonly Uri PatternModulesCsvUri = new("https://azure.github.io/Azure-Verified-Modules/module-indexes/BicepPatternModules.csv");
2325
private static readonly Uri ResourceModulesCsvUri = new("https://azure.github.io/Azure-Verified-Modules/module-indexes/BicepResourceModules.csv");
2426

25-
private static readonly AvmDisplayNameLookup EmptyLookup = new([], [], []);
27+
private static readonly AvmModuleInfoLookup EmptyLookup = new([], [], []);
2628

2729
private readonly IAvmModuleCsvIndexHttpClient client;
2830
private readonly object startLock = new();
2931

30-
private volatile AvmDisplayNameLookup lookup = EmptyLookup;
32+
private volatile AvmModuleInfoLookup lookup = EmptyLookup;
3133
private Task? loadTask;
3234

3335
public AvmModuleDisplayNameProvider(IAvmModuleCsvIndexHttpClient client)
@@ -47,28 +49,52 @@ public bool TryGetModuleDisplayName(string modulePath, [NotNullWhen(true)] out s
4749
{
4850
displayName = null;
4951

52+
if (TryGetModuleInfo(modulePath) is { } info)
53+
{
54+
displayName = info.DisplayName;
55+
return true;
56+
}
57+
58+
return false;
59+
}
60+
61+
public bool TryGetModuleStatus(string modulePath, [NotNullWhen(true)] out string? moduleStatus)
62+
{
63+
moduleStatus = null;
64+
65+
if (TryGetModuleInfo(modulePath) is { } info && info.ModuleStatus is not null)
66+
{
67+
moduleStatus = info.ModuleStatus;
68+
return true;
69+
}
70+
71+
return false;
72+
}
73+
74+
private AvmModuleInfo? TryGetModuleInfo(string modulePath)
75+
{
5076
if (TryGetNormalizedModuleName(modulePath) is not { } normalizedModuleName)
5177
{
52-
return false;
78+
return null;
5379
}
5480

5581
var currentLookup = lookup;
5682
if (normalizedModuleName.StartsWith("avm/utl/", StringComparison.Ordinal))
5783
{
58-
return currentLookup.Utility.TryGetValue(normalizedModuleName, out displayName);
84+
return currentLookup.Utility.TryGetValue(normalizedModuleName, out var info) ? info : null;
5985
}
6086

6187
if (normalizedModuleName.StartsWith("avm/ptn/", StringComparison.Ordinal))
6288
{
63-
return currentLookup.Pattern.TryGetValue(normalizedModuleName, out displayName);
89+
return currentLookup.Pattern.TryGetValue(normalizedModuleName, out var info) ? info : null;
6490
}
6591

6692
if (normalizedModuleName.StartsWith("avm/res/", StringComparison.Ordinal))
6793
{
68-
return currentLookup.Resource.TryGetValue(normalizedModuleName, out displayName);
94+
return currentLookup.Resource.TryGetValue(normalizedModuleName, out var info) ? info : null;
6995
}
7096

71-
return false;
97+
return null;
7298
}
7399

74100
private async Task LoadAsync()
@@ -87,7 +113,7 @@ private async Task LoadAsync()
87113
var pattern = await patternTask;
88114
var resource = await resourceTask;
89115

90-
lookup = new AvmDisplayNameLookup(
116+
lookup = new AvmModuleInfoLookup(
91117
Utility: utility,
92118
Pattern: pattern,
93119
Resource: resource);
@@ -114,7 +140,7 @@ private async Task LoadAsync()
114140
}
115141
}
116142

117-
private async Task<ImmutableDictionary<string, string>> LoadCsvAsync(Uri csvUri, CancellationToken cancellationToken)
143+
private async Task<ImmutableDictionary<string, AvmModuleInfo>> LoadCsvAsync(Uri csvUri, CancellationToken cancellationToken)
118144
{
119145
var csvContent = await client.GetCsvAsync(csvUri, cancellationToken);
120146
using var reader = new StringReader(csvContent);
@@ -140,12 +166,13 @@ private async Task<ImmutableDictionary<string, string>> LoadCsvAsync(Uri csvUri,
140166

141167
var moduleNameIndex = Array.FindIndex(headers, x => string.Equals(x, "ModuleName", StringComparison.Ordinal));
142168
var moduleDisplayNameIndex = Array.FindIndex(headers, x => string.Equals(x, "ModuleDisplayName", StringComparison.Ordinal));
169+
var moduleStatusIndex = Array.FindIndex(headers, x => string.Equals(x, "ModuleStatus", StringComparison.Ordinal));
143170
if (moduleNameIndex == -1 || moduleDisplayNameIndex == -1)
144171
{
145172
return [];
146173
}
147174

148-
var entries = new Dictionary<string, string>(StringComparer.Ordinal);
175+
var entries = new Dictionary<string, AvmModuleInfo>(StringComparer.Ordinal);
149176
while (!parser.EndOfData)
150177
{
151178
var fields = parser.ReadFields();
@@ -167,7 +194,17 @@ private async Task<ImmutableDictionary<string, string>> LoadCsvAsync(Uri csvUri,
167194
continue;
168195
}
169196

170-
entries.TryAdd(normalizedModuleName, moduleDisplayName);
197+
string? moduleStatus = null;
198+
if (moduleStatusIndex != -1 && moduleStatusIndex < fields.Length)
199+
{
200+
var statusValue = fields[moduleStatusIndex]?.Trim();
201+
if (!string.IsNullOrWhiteSpace(statusValue))
202+
{
203+
moduleStatus = statusValue;
204+
}
205+
}
206+
207+
entries.TryAdd(normalizedModuleName, new AvmModuleInfo(moduleDisplayName, moduleStatus));
171208
}
172209

173210
return entries.ToImmutableDictionary(StringComparer.Ordinal);
@@ -189,10 +226,12 @@ private async Task<ImmutableDictionary<string, string>> LoadCsvAsync(Uri csvUri,
189226
return normalized;
190227
}
191228

192-
private sealed record AvmDisplayNameLookup(
193-
ImmutableDictionary<string, string> Utility,
194-
ImmutableDictionary<string, string> Pattern,
195-
ImmutableDictionary<string, string> Resource);
229+
private sealed record AvmModuleInfo(string DisplayName, string? ModuleStatus);
230+
231+
private sealed record AvmModuleInfoLookup(
232+
ImmutableDictionary<string, AvmModuleInfo> Utility,
233+
ImmutableDictionary<string, AvmModuleInfo> Pattern,
234+
ImmutableDictionary<string, AvmModuleInfo> Resource);
196235
}
197236

198237
public interface IAvmModuleCsvIndexHttpClient
@@ -232,5 +271,11 @@ public bool TryGetModuleDisplayName(string modulePath, [NotNullWhen(true)] out s
232271
displayName = null;
233272
return false;
234273
}
274+
275+
public bool TryGetModuleStatus(string modulePath, [NotNullWhen(true)] out string? moduleStatus)
276+
{
277+
moduleStatus = null;
278+
return false;
279+
}
235280
}
236281
}

‎src/Bicep.LangServer/Completions/ModuleReferenceCompletionProvider.cs‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,9 +672,10 @@ private async Task<CompletionItem> ResolveVersionCompletionItem(CompletionItem c
672672
}
673673

674674
var title = GetCompletionTitle(registry, modulePath, metadata.Details.Description);
675+
var status = GetModuleStatus(registry, modulePath);
675676

676677
return completionItem
677-
.WithDocumentation(GetCompletionDocumentation(title, modulePath, metadata.Details, version));
678+
.WithDocumentation(GetCompletionDocumentation(title, modulePath, metadata.Details, status, version));
678679
}
679680

680681
return completionItem;
@@ -694,9 +695,10 @@ private async Task<CompletionItem> ResolveModuleCompletionItem(CompletionItem co
694695
}
695696

696697
var title = GetCompletionTitle(registry, modulePath, details.Description);
698+
var status = GetModuleStatus(registry, modulePath);
697699

698700
return completionItem
699-
.WithDocumentation(GetCompletionDocumentation(title, modulePath, details));
701+
.WithDocumentation(GetCompletionDocumentation(title, modulePath, details, status));
700702
}
701703

702704
return completionItem;
@@ -752,7 +754,18 @@ private static string GetSortText(string label, ModuleCompletionPriority priorit
752754
return defaultTitle;
753755
}
754756

755-
private static string GetCompletionDocumentation(string? title, string modulePath, RegistryMetadataDetails details, string? version = null)
757+
private string? GetModuleStatus(string registry, string modulePath)
758+
{
759+
if (registry.Equals(LanguageConstants.BicepPublicMcrRegistry, StringComparison.Ordinal)
760+
&& avmModuleDisplayNameProvider.TryGetModuleStatus(modulePath, out var moduleStatus))
761+
{
762+
return moduleStatus;
763+
}
764+
765+
return null;
766+
}
767+
768+
private static string GetCompletionDocumentation(string? title, string modulePath, RegistryMetadataDetails details, string? status = null, string? version = null)
756769
{
757770
var displayModulePath = GetDisplayModulePath(modulePath);
758771

@@ -769,6 +782,23 @@ private static string GetCompletionDocumentation(string? title, string modulePat
769782
}
770783

771784
sections.Add($"**Full module path:** {displayModulePath}");
785+
786+
if (status is not null)
787+
{
788+
var emoji = status switch
789+
{
790+
"Proposed" => "⚪",
791+
"Available" => "🟢",
792+
"Orphaned" => "🟡",
793+
"Deprecated" => "🔴",
794+
_ => null,
795+
};
796+
797+
sections.Add(emoji is not null
798+
? $"**Status:** {emoji} {status}"
799+
: $"**Status:** {status}");
800+
}
801+
772802
sections.Add($"**Description:** {details.Description ?? "N/A"}");
773803

774804
if (MarkdownHelper.GetDocumentationLink(details.DocumentationUri) is { } docLink)

0 commit comments

Comments
 (0)