public async Task Goto_definition_works_with_cherrypick_arm_function_import_statements_and_references()

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();
        }
    }
}