in src/Bicep.LangServer.IntegrationTests/DefinitionTests.cs [676:877]
public async Task Goto_definition_works_with_cherrypick_arm_function_import_statements_and_references()
{
var (contents, cursors) = ParserHelper.GetFileWithCursors("""
import {f|o|o| |a|s| |f|i|z|z} from 'mod.json'
var foo = f|i|z|z()
""");
var (moduleContents, moduleCursors) = ParserHelper.GetFileWithCursors($$"""
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"functions": [
{
"namespace": "{{EmitConstants.UserDefinedFunctionsNamespace}}",
"members": {
"foo": {||
"parameters": [],
"output": {
"type": "string",
"value": "foo"
},
"metadata": {
"{{LanguageConstants.MetadataExportedPropertyName}}": true
}
}
}
}
],
"resources": {}
}
""");
using var server = await MultiFileLanguageServerHelper.StartLanguageServer(TestContext, services => services
.WithFileResolver(new InMemoryFileResolver(new Dictionary<Uri, string>
{
{new("file:///mod.json"), moduleContents},
})));
var helper = new ServerRequestHelper(TestContext, server);
var file = await helper.OpenFile("/main.bicep", contents);
foreach (var cursor in cursors)
{
var response = await file.GotoDefinition(cursor);
var expectedRange = PositionHelper.GetRange(TextCoordinateConverter.GetLineStarts(moduleContents), moduleCursors[0], moduleCursors[1]);
response.TargetUri.Path.Should().Be("/mod.json");
response.TargetRange.Should().Be(expectedRange);
}
}
[TestMethod]
public async Task Goto_definition_works_with_arm_wildcard_instance_function_invocation()
{
var (contents, cursors) = ParserHelper.GetFileWithCursors("""
import * as mod from 'mod.json'
var foo = mod.f|o|o()
""");
var (moduleContents, moduleCursors) = ParserHelper.GetFileWithCursors($$"""
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"functions": [
{
"namespace": "{{EmitConstants.UserDefinedFunctionsNamespace}}",
"members": {
"foo": {||
"parameters": [],
"output": {
"type": "string",
"value": "foo"
},
"metadata": {
"{{LanguageConstants.MetadataExportedPropertyName}}": true
}
}
}
}
],
"resources": {}
}
""");
using var server = await MultiFileLanguageServerHelper.StartLanguageServer(TestContext, services => services
.WithFileResolver(new InMemoryFileResolver(new Dictionary<Uri, string>
{
{new("file:///mod.json"), moduleContents},
})));
var helper = new ServerRequestHelper(TestContext, server);
var file = await helper.OpenFile("/main.bicep", contents);
foreach (var cursor in cursors)
{
var response = await file.GotoDefinition(cursor);
var expectedRange = PositionHelper.GetRange(TextCoordinateConverter.GetLineStarts(moduleContents), moduleCursors[0], moduleCursors[1]);
response.TargetUri.Path.Should().Be("/mod.json");
response.TargetRange.Should().Be(expectedRange);
}
}
private static async Task RunDefinitionScenarioTest(TestContext testContext, string fileWithCursors, char cursor, Action<List<LocationOrLocationLinks>> assertAction)
{
var (file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors, cursor);
var bicepFile = new LanguageClientFile($"{testContext.TestName}/path/to/main.bicep", file);
var helper = await DefaultServer.GetAsync();
await helper.OpenFileOnceAsync(testContext, file, bicepFile.Uri);
var results = await RequestDefinitions(helper.Client, bicepFile, cursors);
assertAction(results);
}
private static async Task RunDefinitionScenarioTestWithFiles(
TestContext testContext,
string fileWithCursors,
char cursor,
Action<List<LocationOrLocationLinks>> assertAction,
IEnumerable<(string fileName, string fileBody)> additionalFiles)
{
var (file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors, cursor);
var bicepFile = new LanguageClientFile($"{testContext.TestName}/path/to/main.bicep", file);
var server = new SharedLanguageHelperManager();
var fileResolver = new InMemoryFileResolver(additionalFiles.ToDictionary(x => new Uri($"file:///{testContext.TestName}/path/to/{x.fileName}"), x => x.fileBody));
var fileExplorer = new FileSystemFileExplorer(fileResolver.MockFileSystem);
server.Initialize(async () => await MultiFileLanguageServerHelper.StartLanguageServer(testContext, services =>
services.WithFileResolver(fileResolver).WithFileExplorer(fileExplorer)));
var helper = await server.GetAsync();
await helper.OpenFileOnceAsync(testContext, file, bicepFile.Uri);
var results = await RequestDefinitions(helper.Client, bicepFile, cursors);
assertAction(results);
await server.DisposeAsync();
}
private static async Task<List<LocationOrLocationLinks>> RequestDefinitions(ILanguageClient client, LanguageClientFile bicepFile, IEnumerable<int> cursors)
{
var results = new List<LocationOrLocationLinks>();
foreach (var cursor in cursors)
{
var result = await client.RequestDefinition(new DefinitionParams()
{
TextDocument = bicepFile.Uri,
Position = bicepFile.GetPosition(cursor),
});
results.Add(result!);
}
return results;
}
private bool ValidUnboundNode(List<SyntaxBase> accumulated, int index)
{
// Module path
if (index > 1 &&
accumulated[index] is StringSyntax &&
accumulated[index - 1] is IdentifierSyntax &&
accumulated[index - 2] is ModuleDeclarationSyntax)
{
return true;
}
// import path
if (index > 1 &&
accumulated[index] is StringSyntax &&
accumulated[index - 1] is CompileTimeImportFromClauseSyntax
)
{
return true;
}
return false;
}
private static LocationLink ValidateDefinitionResponse(LocationOrLocationLinks response)
{
// go to def should produce single result in all cases
response.Should().HaveCount(1);
var single = response.Single();
single.IsLocation.Should().BeFalse();
single.IsLocationLink.Should().BeTrue();
single.Location.Should().BeNull();
single.LocationLink.Should().NotBeNull();
return single.LocationLink!;
}
private static IEnumerable<object[]> GetData()
{
return DataSets.NonStressDataSets.ToDynamicTestData();
}
}
}