Skip to content

Commit

Permalink
Add MS Docs link to resource symbol hovers (#5782)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-c-martin authored and davidcho23 committed Feb 1, 2022
1 parent 98b1b01 commit ca093ad
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 12 deletions.
35 changes: 33 additions & 2 deletions src/Bicep.LangServer.IntegrationTests/HoverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ var test|Param string
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my module\n"),
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my param\n"),
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my var\n"),
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my \nmultiline \nresource\n"),
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my output\n"));
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my \nmultiline \nresource \n[View Type Documentation](https://docs.microsoft.com/azure/templates/test.rp/discriminatortests?tabs=bicep)\n"),
h => h!.Contents.MarkupContent!.Value.Should().EndWith("```\nthis is my output \n\n"));
}

[TestMethod]
Expand Down Expand Up @@ -377,6 +377,37 @@ public async Task Function_hovers_include_descriptions_if_function_overload_has_
h => h!.Contents.MarkupContent!.Value.Should().Be("```bicep\nfunction concat('abc', 'def'): string\n```\nCombines multiple string, integer, or boolean values and returns them as a concatenated string.\n"));
}

[TestMethod]
public async Task Resource_hovers_should_include_documentation_links_for_known_resource_types()
{
var hovers = await RequestHoversAtCursorLocations(@"
resource fo|o 'Test.Rp/basicTests@2020-01-01' = {}
@description('This resource also has a description!')
resource b|ar 'Test.Rp/basicTests@2020-01-01' = {}
resource m|adeUp 'Test.MadeUp/nonExistentResourceType@2020-01-01' = {}
");

hovers.Should().SatisfyRespectively(
h => h!.Contents.MarkupContent!.Value.Should().BeEquivalentToIgnoringNewlines(@"```bicep
resource foo 'Test.Rp/basicTests@2020-01-01'
```
[View Type Documentation](https://docs.microsoft.com/azure/templates/test.rp/basictests?tabs=bicep)
"),
h => h!.Contents.MarkupContent!.Value.Should().BeEquivalentToIgnoringNewlines(@"```bicep
resource bar 'Test.Rp/basicTests@2020-01-01'
```
This resource also has a description!
[View Type Documentation](https://docs.microsoft.com/azure/templates/test.rp/basictests?tabs=bicep)
"),
h => h!.Contents.MarkupContent!.Value.Should().BeEquivalentToIgnoringNewlines(@"```bicep
resource madeUp 'Test.MadeUp/nonExistentResourceType@2020-01-01'
```
"));
}

[TestMethod]
public async Task Function_hovers_display_without_descriptions_if_function_overload_has_not_been_resolved()
{
Expand Down
49 changes: 41 additions & 8 deletions src/Bicep.LangServer/Handlers/BicepHoverHandler.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Bicep.Core.Semantics;
using Bicep.Core.Semantics.Namespaces;
using Bicep.Core.Syntax;
using Bicep.Core.TypeSystem;
using Bicep.LanguageServer.Providers;
using Bicep.LanguageServer.Utils;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
Expand Down Expand Up @@ -50,39 +53,54 @@ public BicepHoverHandler(ISymbolResolver symbolResolver)
});
}

private static string? TryGetDescriptionMarkdown(SymbolResolutionResult result, DeclaredSymbol symbol)
{
if (symbol.DeclaringSyntax is StatementSyntax statementSyntax &&
SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), statementSyntax) is {} description)
{
return description;
}

return null;
}

private static string? GetMarkdown(HoverParams request, SymbolResolutionResult result)
{
// all of the generated markdown includes the language id to avoid VS code rendering
// all of the generated markdown includes the language id to avoid VS code rendering
// with multiple borders
switch (result.Symbol)
{
case ImportedNamespaceSymbol import:
return CodeBlockWithDescription(
$"import {import.Name}", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), import.DeclaringImport));
$"import {import.Name}", TryGetDescriptionMarkdown(result, import));

case ParameterSymbol parameter:
return CodeBlockWithDescription(
$"param {parameter.Name}: {parameter.Type}", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), parameter.DeclaringParameter));
$"param {parameter.Name}: {parameter.Type}", TryGetDescriptionMarkdown(result, parameter));

case VariableSymbol variable:
return CodeBlockWithDescription($"var {variable.Name}: {variable.Type}", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), variable.DeclaringVariable));
return CodeBlockWithDescription($"var {variable.Name}: {variable.Type}", TryGetDescriptionMarkdown(result, variable));

case ResourceSymbol resource:
var docsSuffix = TryGetTypeDocumentationLink(resource) is {} typeDocsLink ? $"[View Type Documentation]({typeDocsLink})" : "";
var description = TryGetDescriptionMarkdown(result, resource);

return CodeBlockWithDescription(
$"resource {resource.Name}\n{resource.Type}", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), resource.DeclaringResource));
$"resource {resource.Name} {(resource.Type is ResourceType ? $"'{resource.Type}'" : resource.Type)}",
description is {} ? $"{description}\n{docsSuffix}" : docsSuffix);

case ModuleSymbol module:
var filePath = SyntaxHelper.TryGetModulePath(module.DeclaringModule, out _);
if (filePath != null)
{
return CodeBlockWithDescription($"module {module.Name}\n'{filePath}'", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), module.DeclaringModule));
return CodeBlockWithDescription($"module {module.Name} '{filePath}'", TryGetDescriptionMarkdown(result, module));
}

return CodeBlockWithDescription($"module {module.Name}", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), module.DeclaringModule));
return CodeBlockWithDescription($"module {module.Name}", TryGetDescriptionMarkdown(result, module));

case OutputSymbol output:
return CodeBlockWithDescription(
$"output {output.Name}: {output.Type}", SemanticModelHelper.TryGetDescription(result.Context.Compilation.GetEntrypointSemanticModel(), output.DeclaringOutput));
$"output {output.Name}: {output.Type}", TryGetDescriptionMarkdown(result, output));

case BuiltInNamespaceSymbol builtInNamespace:
return CodeBlock($"{builtInNamespace.Name} namespace");
Expand Down Expand Up @@ -147,6 +165,21 @@ private static string GetFunctionMarkdown(FunctionSymbol function, FunctionCallS
return CodeBlock(buffer.ToString());
}

private static string? TryGetTypeDocumentationLink(ResourceSymbol resource)
{
if (resource.TryGetResourceType() is {} resourceType &&
resourceType.DeclaringNamespace.ProviderNameEquals(AzNamespaceType.BuiltInName) &&
resourceType.DeclaringNamespace.ResourceTypeProvider.HasDefinedType(resourceType.TypeReference))
{
var provider = resourceType.TypeReference.TypeSegments.First().ToLowerInvariant();
var typePath = resourceType.TypeReference.TypeSegments.Skip(1).Select(x => x.ToLowerInvariant());

return $"https://docs.microsoft.com/azure/templates/{provider}/{string.Join('/', typePath)}?tabs=bicep";
}

return null;
}

protected override HoverRegistrationOptions CreateRegistrationOptions(HoverCapability capability, ClientCapabilities clientCapabilities) => new()
{
DocumentSelector = DocumentSelectorFactory.Create()
Expand Down
5 changes: 3 additions & 2 deletions src/vscode-bicep/src/test/e2e/hover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ describe("hover", (): void => {
endLine: 108,
endCharacter: 13,
contents: [
codeblock(
"resource vnet\nMicrosoft.Network/virtualNetworks@2020-06-01"
codeblockWithDescription(
"resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01'",
"[View Type Documentation](https://docs.microsoft.com/azure/templates/microsoft.network/virtualnetworks?tabs=bicep)"
),
],
});
Expand Down

0 comments on commit ca093ad

Please sign in to comment.