Example #1
0
        public async Task Overlapping_tokens_are_not_returned(DataSet dataSet)
        {
            var uri       = DocumentUri.From($"/{dataSet.Name}");
            var bicepFile = SourceFileFactory.CreateBicepFile(uri.ToUri(), dataSet.Bicep);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(TestContext, dataSet.Bicep, uri);

            var client = helper.Client;

            var semanticTokens = await client.TextDocument.RequestSemanticTokens(new SemanticTokensParams
            {
                TextDocument = new TextDocumentIdentifier(uri),
            });

            var tokenSpans = CalculateTokenTextSpans(bicepFile.LineStarts, semanticTokens !.Data).ToArray();

            for (var i = 1; i < tokenSpans.Length; i++)
            {
                var currentSpan = tokenSpans[i];
                var prevSpan    = tokenSpans[i - 1];

                if (TextSpan.AreOverlapping(prevSpan, currentSpan))
                {
                    using (new AssertionScope()
                           .WithAnnotations(bicepFile, "overlapping tokens", new[] { prevSpan, currentSpan }, _ => "here", x => x.ToRange(bicepFile.LineStarts)))
                    {
                        TextSpan.AreOverlapping(prevSpan, currentSpan).Should().BeFalse();
                    }
                }
            }
        }
        public async Task GoToDefinitionRequestOnUnsupportedOrInvalidSyntaxNodeShouldReturnEmptyResponse(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client = helper.Client;

            var(compilation, _, _) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            var undeclaredSymbolBindings = symbolTable.Where(pair => pair.Value is not DeclaredSymbol and not PropertySymbol);

            foreach (var(syntax, _) in undeclaredSymbolBindings)
            {
                var response = await client.RequestDefinition(new DefinitionParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = IntegrationTestHelper.GetPosition(lineStarts, syntax)
                });

                using (new AssertionScope().WithVisualCursor(compilation.SourceFileGrouping.EntryPoint, syntax.Span))
                {
                    // go to definition on a symbol that isn't declared by the user (like error or function symbol)
                    // should produce an empty response
                    response.Should().BeEmpty();
                }
            }
        }
Example #3
0
        public async Task VerifyDisableNextLineCodeActionInvocationFiresTelemetryEvent()
        {
            var bicepFileContents = @"param storageAccount string = 'testStorageAccount'";
            var bicepFilePath     = FileHelper.SaveResultFile(TestContext, "main.bicep", bicepFileContents);
            var documentUri       = DocumentUri.FromFileSystemPath(bicepFilePath);
            var uri = documentUri.ToUri();

            var files = new Dictionary <Uri, string>
            {
                [uri] = bicepFileContents,
            };

            var compilation = new Compilation(BicepTestConstants.NamespaceProvider, SourceFileGroupingFactory.CreateForFiles(files, uri, BicepTestConstants.FileResolver, BicepTestConstants.BuiltInConfiguration), BicepTestConstants.BuiltInConfiguration);
            var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics();

            var telemetryReceived = new TaskCompletionSource <BicepTelemetryEvent>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      TestContext,
                      options =>
            {
                options.OnTelemetryEvent <BicepTelemetryEvent>(telemetry => telemetryReceived.SetResult(telemetry));
            },
                      new Server.CreationOptions(NamespaceProvider: BicepTestConstants.NamespaceProvider, FileResolver: new InMemoryFileResolver(files)));

            var client = helper.Client;

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(documentUri, files[uri], 1));

            var lineStarts = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            var codeActions = await client.RequestCodeAction(new CodeActionParams
            {
                TextDocument = new TextDocumentIdentifier(documentUri),
                Range        = diagnostics.First().ToRange(lineStarts)
            });

            var disableNextLineCodeAction = codeActions.First(x => x.CodeAction !.Title == "Disable no-unused-params").CodeAction;

            _ = await client !.ResolveCodeAction(disableNextLineCodeAction !);

            Command?command   = disableNextLineCodeAction !.Command;
            JArray? arguments = command !.Arguments;

            await client.Workspace.ExecuteCommand(command);

            var bicepTelemetryEvent = await IntegrationTestHelper.WithTimeoutAsync(telemetryReceived.Task);

            IDictionary <string, string> properties = new Dictionary <string, string>
            {
                { "code", "no-unused-params" }
            };

            bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.DisableNextLineDiagnostics);
            bicepTelemetryEvent.Properties.Should().Equal(properties);
        }
        public async Task NonExistentUriShouldProvideNoSignatureHelp()
        {
            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, string.Empty, DocumentUri.From("/fake.bicep"));

            var client = helper.Client;

            var signatureHelp = await RequestSignatureHelp(client, new Position(0, 0), DocumentUri.From("/fake2.bicep"));

            signatureHelp.Should().BeNull();
        }
        public async Task GoToDefinitionRequestOnValidSymbolReferenceShouldReturnLocationOfDeclaredSymbol(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client      = helper.Client;
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            // filter out symbols that don't have locations as well as locals with invalid identifiers
            // (locals are special because their full span is the same as the identifier span,
            // which makes it impossible to go to definition on a local with invalid identifiers)
            var declaredSymbolBindings = symbolTable
                                         .Where(pair => pair.Value is DeclaredSymbol && (pair.Value is not LocalVariableSymbol local || local.NameSyntax.IsValid))
                                         .Select(pair => new KeyValuePair <SyntaxBase, DeclaredSymbol>(pair.Key, (DeclaredSymbol)pair.Value));

            foreach (var(syntax, symbol) in declaredSymbolBindings)
            {
                var response = await client.RequestDefinition(new DefinitionParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = IntegrationTestHelper.GetPosition(lineStarts, syntax)
                });

                var link = ValidateDefinitionResponse(response);

                // document should match the requested document
                link.TargetUri.Should().Be(uri);

                // target range should be the whole span of the symbol
                link.TargetRange.Should().Be(symbol.DeclaringSyntax.Span.ToRange(lineStarts));

                // selection range should be the span of the identifier of the symbol
                link.TargetSelectionRange.Should().Be(symbol.NameSyntax.Span.ToRange(lineStarts));

                if (syntax is ParameterDeclarationSyntax parameterSyntax)
                {
                    // we only underline the key of the param declaration syntax
                    link.OriginSelectionRange.Should().Be(parameterSyntax.Name.ToRange(lineStarts));
                }
                else if (syntax is ITopLevelNamedDeclarationSyntax namedSyntax)
                {
                    // Instead of underlining everything, we only underline the resource name
                    link.OriginSelectionRange.Should().Be(namedSyntax.Name.ToRange(lineStarts));
                }
                else
                {
                    // origin selection range should be the span of the syntax node that references the symbol
                    link.OriginSelectionRange.Should().Be(syntax.ToRange(lineStarts));
                }
            }
        }
Example #6
0
        public async Task RequestDocumentFormattingShouldReturnFullRangeTextEdit()
        {
            var documentUri         = DocumentUri.From("/template.bicep");
            var diagnosticsReceived = new TaskCompletionSource <PublishDiagnosticsParams>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(this.TestContext, options =>
            {
                options.OnPublishDiagnostics(diagnostics =>
                {
                    diagnosticsReceived.SetResult(diagnostics);
                });
            });

            var client = helper.Client;

            // client opens the document
            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(documentUri, @"
param myParam object

// line comment
var myVar1 = 1 + mod(1, 2)
var myVar2 = myParam.foo[1]

resource myResource 'myRP/provider@2020-11-01' = {
/* block
  comment
  */
  name: 'test'
}

module myModule './module.bicep' = {
  name: 'test' 
}

output myOutput string = 'value'", 0));

            // client requests symbols
            var textEditContainer = await client.TextDocument.RequestDocumentFormatting(new DocumentFormattingParams
            {
                TextDocument = new TextDocumentIdentifier
                {
                    Uri = documentUri
                },
                Options = new FormattingOptions
                {
                    TabSize            = 4,
                    InsertSpaces       = true,
                    InsertFinalNewline = true,
                }
            });

            textEditContainer.Should().NotBeNull();
            textEditContainer.Should().HaveCount(1);
        }
        private static async Task RunDefinitionScenarioTest(TestContext testContext, string fileWithCursors, Action <List <LocationOrLocationLinks> > assertAction)
        {
            var(file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
            var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///path/to/main.bicep"), file);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(testContext, file, bicepFile.FileUri, creationOptions : new LanguageServer.Server.CreationOptions(NamespaceProvider: BuiltInTestTypes.Create()));

            var client  = helper.Client;
            var results = await RequestDefinitions(client, bicepFile, cursors);

            assertAction(results);
        }
Example #8
0
        private async Task VerifyBicepFileOpenTelemetry(
            string?bicepConfigFileContents,
            string mainBicepFileContents,
            string referencedBicepFileContents,
            IDictionary <string, string>?linterRuleStateOnBicepFileOpenTelemetryEventProperties,
            IDictionary <string, string> bicepFileOpenTelemetryEventProperties)
        {
            var testOutputPath = Path.Combine(TestContext.ResultsDirectory, Guid.NewGuid().ToString());

            if (bicepConfigFileContents is not null)
            {
                FileHelper.SaveResultFile(TestContext, "bicepconfig.json", bicepConfigFileContents, testOutputPath);
            }

            var fileSystemDict = new Dictionary <Uri, string>();

            var mainBicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", mainBicepFileContents, testOutputPath);
            var mainUri           = DocumentUri.FromFileSystemPath(mainBicepFilePath);

            fileSystemDict[mainUri.ToUri()] = mainBicepFileContents;

            var referencedBicepFilePath = FileHelper.SaveResultFile(TestContext, "groups.bicep", referencedBicepFileContents, testOutputPath);
            var moduleUri = DocumentUri.FromFileSystemPath(referencedBicepFilePath);

            fileSystemDict[moduleUri.ToUri()] = referencedBicepFileContents;

            var telemetryEventsListener = new MultipleMessageListener <BicepTelemetryEvent>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      TestContext,
                      options =>
            {
                options.OnTelemetryEvent <BicepTelemetryEvent>(telemetry => telemetryEventsListener.AddMessage(telemetry));
            },
                      creationOptions : new Server.CreationOptions(FileResolver: new InMemoryFileResolver(fileSystemDict)));

            var client = helper.Client;

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(mainUri, fileSystemDict[mainUri.ToUri()], 1));

            var bicepTelemetryEvent = await telemetryEventsListener.WaitNext();

            if (linterRuleStateOnBicepFileOpenTelemetryEventProperties is not null)
            {
                bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.LinterRuleStateOnBicepFileOpen);
                bicepTelemetryEvent.Properties.Should().Contain(linterRuleStateOnBicepFileOpenTelemetryEventProperties);
            }

            bicepTelemetryEvent = await telemetryEventsListener.WaitNext();

            bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.BicepFileOpen);
            bicepTelemetryEvent.Properties.Should().Contain(bicepFileOpenTelemetryEventProperties);
        }
        private async Task <LanguageServerHelper> StartServerWithClientConnectionAsync(MultipleMessageListener <PublishDiagnosticsParams> diagsListener)
        {
            var fileSystemDict = new Dictionary <Uri, string>();
            var fileResolver   = new InMemoryFileResolver(fileSystemDict);
            var serverOptions  = new Server.CreationOptions(FileResolver: fileResolver);

            return(await LanguageServerHelper.StartServerWithClientConnectionAsync(
                       TestContext,
                       options =>
            {
                options.OnPublishDiagnostics(diags => diagsListener.AddMessage(diags));
            },
                       serverOptions));
        }
        public async Task GoToDefinitionOnUnboundSyntaxNodeShouldReturnEmptyResponse(DataSet dataSet)
        {
            // local function
            bool IsUnboundNode(IDictionary <SyntaxBase, Symbol> dictionary, SyntaxBase syntax) => dictionary.ContainsKey(syntax) == false && !(syntax is Token);

            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client      = helper.Client;
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            var unboundNodes = SyntaxAggregator.Aggregate(
                source: compilation.SourceFileGrouping.EntryPoint.ProgramSyntax,
                seed: new List <SyntaxBase>(),
                function: (accumulated, syntax) =>
            {
                if (IsUnboundNode(symbolTable, syntax) && !(syntax is ProgramSyntax))
                {
                    // only collect unbound nodes non-program nodes
                    accumulated.Add(syntax);
                }

                return(accumulated);
            },
                resultSelector: accumulated => accumulated,
                // visit children only if current node is not bound
                continuationFunction: (accumulated, syntax) => IsUnboundNode(symbolTable, syntax));

            for (int i = 0; i < unboundNodes.Count(); i++)
            {
                var syntax = unboundNodes[i];
                if (ValidUnboundNode(unboundNodes, i))
                {
                    continue;
                }
                var response = await client.RequestDefinition(new DefinitionParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = IntegrationTestHelper.GetPosition(lineStarts, syntax)
                });

                // go to definition on a syntax node that isn't bound to a symbol should produce an empty response
                response.Should().BeEmpty();
            }
        }
Example #11
0
        public async Task FindReferencesWithoutDeclarationsShouldProduceCorrectResults(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client      = helper.Client;
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            // filter out bind failures and locals with invalid identifiers
            // (locals are special because their span is equal to their identifier span)
            var filteredSymbolTable = symbolTable.Where(pair => pair.Value.Kind != SymbolKind.Error && (pair.Value is not LocalVariableSymbol local || local.NameSyntax.IsValid));

            // TODO: Implement for PropertySymbol
            filteredSymbolTable = filteredSymbolTable.Where(pair => pair.Value is not PropertySymbol);
            var symbolToSyntaxLookup = filteredSymbolTable.ToLookup(pair => pair.Value, pair => pair.Key);

            foreach (var(syntax, symbol) in filteredSymbolTable)
            {
                var locations = await client.RequestReferences(new ReferenceParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Context      = new ReferenceContext
                    {
                        IncludeDeclaration = false
                    },
                    Position = IntegrationTestHelper.GetPosition(lineStarts, syntax)
                });

                // all URIs should be the same in the results
                locations.Select(r => r.Uri).Should().AllBeEquivalentTo(uri);

                // exclude declarations when calculating expected ranges
                var expectedRanges = symbolToSyntaxLookup[symbol]
                                     .Where(node => !(node is INamedDeclarationSyntax))
                                     .Select(node => PositionHelper.GetNameRange(lineStarts, node));

                using (new AssertionScope()
                       .WithAnnotations(compilation.SourceFileGrouping.EntryPoint, "expected", expectedRanges, _ => "here", x => x)
                       .WithAnnotations(compilation.SourceFileGrouping.EntryPoint, "actual", locations, _ => "here", x => x.Range))
                {
                    // ranges should match what we got from our own symbol table
                    locations.Select(l => l.Range).Should().BeEquivalentTo(expectedRanges);
                }
            }
        }
Example #12
0
        private async Task <BicepTelemetryEvent> ResolveCompletionAsync(string text, string prefix, Position position)
        {
            var fileSystemDict          = new Dictionary <Uri, string>();
            var telemetryEventsListener = new MultipleMessageListener <BicepTelemetryEvent>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      TestContext,
                      options =>
            {
                options.OnTelemetryEvent <BicepTelemetryEvent>(telemetry => telemetryEventsListener.AddMessage(telemetry));
            },
                      new Server.CreationOptions(NamespaceProvider: BicepTestConstants.NamespaceProvider, FileResolver: new InMemoryFileResolver(fileSystemDict)));

            var client = helper.Client;

            var mainUri = DocumentUri.FromFileSystemPath("/main.bicep");

            fileSystemDict[mainUri.ToUri()] = text;

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(mainUri, fileSystemDict[mainUri.ToUri()], 1));

            var bicepTelemetryEvent = await telemetryEventsListener.WaitNext();

            bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.LinterRuleStateOnBicepFileOpen);

            bicepTelemetryEvent = await telemetryEventsListener.WaitNext();

            bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.BicepFileOpen);

            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = new TextDocumentIdentifier(mainUri),
                Position     = position,
            });

            CompletionItem      completionItem = completions.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == prefix).First();
            Command?            command        = completionItem.Command;
            JArray?             arguments      = command !.Arguments;
            BicepTelemetryEvent?telemetryEvent = arguments !.First().ToObject <BicepTelemetryEvent>();

            await client.ResolveCompletion(completionItem);

            await client.Workspace.ExecuteCommand(command);

            return(await telemetryEventsListener.WaitNext());
        }
Example #13
0
        public async Task RequestingCodeActionWithFixableDiagnosticsShouldProduceQuickFixes(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(this.TestContext);

            var uri = DocumentUri.From(fileUri);

            // start language server
            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri, creationOptions : new LanguageServer.Server.CreationOptions(FileResolver: new FileResolver()));

            var client = helper.Client;

            // construct a parallel compilation
            var lineStarts = compilation.SourceFileGrouping.EntryPoint.LineStarts;
            var fixables   = compilation.GetEntrypointSemanticModel().GetAllDiagnostics().OfType <IFixable>();

            foreach (IFixable fixable in fixables)
            {
                foreach (var span in GetOverlappingSpans(fixable.Span))
                {
                    CommandOrCodeActionContainer?quickFixes = await client.RequestCodeAction(new CodeActionParams
                    {
                        TextDocument = new TextDocumentIdentifier(uri),
                        Range        = span.ToRange(lineStarts)
                    });

                    // Assert.
                    quickFixes.Should().NotBeNull();

                    var quickFixList = quickFixes.Where(x => x.CodeAction?.Kind == CodeActionKind.QuickFix).ToList();
                    var bicepFixList = fixable.Fixes.ToList();

                    quickFixList.Should().HaveSameCount(bicepFixList);

                    for (int i = 0; i < quickFixList.Count; i++)
                    {
                        var quickFix = quickFixList[i];
                        var bicepFix = bicepFixList[i];

                        quickFix.IsCodeAction.Should().BeTrue();
                        quickFix.CodeAction !.Kind.Should().Be(CodeActionKind.QuickFix);
                        quickFix.CodeAction.Title.Should().Be(bicepFix.Description);
                        quickFix.CodeAction.Edit !.Changes.Should().ContainKey(uri);

                        var textEditList    = quickFix.CodeAction.Edit.Changes ![uri].ToList();
        public async Task ShouldProvideSignatureHelpBetweenFunctionParentheses(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client      = helper.Client;
            var symbolTable = compilation.ReconstructSymbolTable();
            var tree        = compilation.SourceFileGrouping.EntryPoint;

            var functionCalls = SyntaxAggregator.Aggregate(
                tree.ProgramSyntax,
                new List <FunctionCallSyntaxBase>(),
                (accumulated, current) =>
            {
                if (current is FunctionCallSyntaxBase functionCallBase)
                {
                    accumulated.Add(functionCallBase);
                }

                return(accumulated);
            },
                accumulated => accumulated);

            foreach (FunctionCallSyntaxBase functionCall in functionCalls)
            {
                var expectDecorator = compilation.GetEntrypointSemanticModel().Binder.GetParent(functionCall) is DecoratorSyntax;

                var symbol = compilation.GetEntrypointSemanticModel().GetSymbolInfo(functionCall);

                // if the cursor is present immediate after the function argument opening paren,
                // the signature help can only show the signature of the enclosing function
                var startOffset = functionCall.OpenParen.GetEndPosition();
                await ValidateOffset(client, uri, tree, startOffset, symbol as FunctionSymbol, expectDecorator);

                // if the cursor is present immediately before the function argument closing paren,
                // the signature help can only show the signature of the enclosing function
                var endOffset = functionCall.CloseParen.Span.Position;
                await ValidateOffset(client, uri, tree, endOffset, symbol as FunctionSymbol, expectDecorator);
            }
        }
Example #15
0
        public async Task HighlightsShouldShowAllReferencesOfTheSymbol(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client      = helper.Client;
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            // filter out binding failures and locals with invalid identifiers
            // (locals are special because their full span is the same as the identifier span,
            // which makes it impossible to highlight locals with invalid identifiers)
            var filteredSymbolTable = symbolTable.Where(pair => pair.Value.Kind != SymbolKind.Error && (pair.Value is not LocalVariableSymbol local || local.NameSyntax.IsValid));

            // TODO: Implement for PropertySymbol
            filteredSymbolTable = filteredSymbolTable.Where(pair => pair.Value is not PropertySymbol);

            var symbolToSyntaxLookup = filteredSymbolTable.ToLookup(pair => pair.Value, pair => pair.Key);

            foreach (var(syntax, symbol) in filteredSymbolTable)
            {
                var highlights = await client.RequestDocumentHighlight(new DocumentHighlightParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = IntegrationTestHelper.GetPosition(lineStarts, syntax)
                });

                // calculate expected highlights
                var expectedHighlights = symbolToSyntaxLookup[symbol].Select(node => CreateExpectedHighlight(lineStarts, node));

                using (new AssertionScope()
                       .WithAnnotations(compilation.SourceFileGrouping.EntryPoint, "expected", expectedHighlights, _ => "here", x => x.Range)
                       .WithAnnotations(compilation.SourceFileGrouping.EntryPoint, "actual", highlights, _ => "here", x => x.Range))
                {
                    // ranges should match what we got from our own symbol table
                    highlights.Should().BeEquivalentTo(expectedHighlights);
                }
            }
        }
Example #16
0
        public async Task RenamingIdentifierAccessOrDeclarationShouldRenameDeclarationAndAllReferences(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client      = helper.Client;
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            var symbolToSyntaxLookup = symbolTable
                                       .Where(pair => pair.Value.Kind != SymbolKind.Error)
                                       .ToLookup(pair => pair.Value, pair => pair.Key);

            var validVariableAccessPairs = symbolTable
                                           .Where(pair => (pair.Key is VariableAccessSyntax || pair.Key is ResourceAccessSyntax || pair.Key is ITopLevelNamedDeclarationSyntax) &&
                                                  pair.Value.Kind != SymbolKind.Error &&
                                                  pair.Value.Kind != SymbolKind.Function &&
                                                  pair.Value.Kind != SymbolKind.Namespace
                                                  // symbols whose identifiers have parse errors will have a name like <error> or <missing>
                                                  && pair.Value.Name.Contains("<") == false);

            const string expectedNewText = "NewIdentifier";

            foreach (var(syntax, symbol) in validVariableAccessPairs)
            {
                var edit = await client.RequestRename(new RenameParams
                {
                    NewName      = expectedNewText,
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = IntegrationTestHelper.GetPosition(lineStarts, syntax)
                });

                edit.Should().NotBeNull();
                edit !.DocumentChanges.Should().BeNullOrEmpty();
                edit.Changes.Should().NotBeNull();
                edit.Changes.Should().HaveCount(1);
                edit.Changes.Should().ContainKey(uri);

                var textEdits = edit.Changes ![uri];
Example #17
0
        public async Task Build_command_should_generate_template_with_symbolic_names_if_enabled()
        {
            var diagnosticsListener = new MultipleMessageListener <PublishDiagnosticsParams>();
            var features            = BicepTestConstants.CreateFeaturesProvider(
                TestContext,
                symbolicNameCodegenEnabled: true,
                assemblyFileVersion: BicepTestConstants.DevAssemblyFileVersion);

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      this.TestContext,
                      options => options.OnPublishDiagnostics(diagnosticsParams => diagnosticsListener.AddMessage(diagnosticsParams)),
                      new LanguageServer.Server.CreationOptions(
                          NamespaceProvider: BuiltInTestTypes.Create(),
                          Features: features));

            var client = helper.Client;

            var outputDirectory = FileHelper.SaveEmbeddedResourcesWithPathPrefix(
                TestContext,
                typeof(ExamplesTests).Assembly,
                "Bicep.Core.Samples/Resources_CRLF");

            var bicepFilePath = Path.Combine(outputDirectory, "main.bicep");
            var expectedJson  = File.ReadAllText(Path.Combine(outputDirectory, "main.symbolicnames.json"));

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParamsFromFile(bicepFilePath, 1));
            await diagnosticsListener.WaitNext();

            await client.Workspace.ExecuteCommand(new Command
            {
                Name      = "build",
                Arguments = new JArray {
                    bicepFilePath,
                }
            });

            var buildCommandOutput = File.ReadAllText(Path.ChangeExtension(bicepFilePath, ".json"));

            buildCommandOutput.Should().BeEquivalentToIgnoringNewlines(expectedJson);
        }
        public async Task NonFunctionCallSyntaxShouldProvideNoSignatureHelp(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            var client    = helper.Client;
            var bicepFile = compilation.SourceFileGrouping.EntryPoint;

            var nonFunctions = SyntaxAggregator.Aggregate(
                bicepFile.ProgramSyntax,
                new List <SyntaxBase>(),
                (accumulated, current) =>
            {
                if (current is not FunctionCallSyntaxBase)
                {
                    accumulated.Add(current);
                }

                return(accumulated);
            },
                accumulated => accumulated,
                // requesting signature help on non-function nodes that are placed inside function call nodes will produce signature help
                // since we don't want that, stop the visitor from visiting inner nodes when a function call is encountered
                (accumulated, current) => current is not FunctionCallSyntaxBase);

            foreach (var nonFunction in nonFunctions)
            {
                using (new AssertionScope().WithVisualCursor(bicepFile, nonFunction.Span.ToZeroLengthSpan()))
                {
                    var position      = PositionHelper.GetPosition(bicepFile.LineStarts, nonFunction.Span.Position);
                    var signatureHelp = await RequestSignatureHelp(client, position, uri);

                    signatureHelp.Should().BeNull();
                }
            }
        }
Example #19
0
        private async Task <BicepTelemetryEvent> ResolveCompletionAsync(string text, string prefix, Position position)
        {
            var fileSystemDict    = new Dictionary <Uri, string>();
            var telemetryReceived = new TaskCompletionSource <BicepTelemetryEvent>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      TestContext,
                      options =>
            {
                options.OnTelemetryEvent <BicepTelemetryEvent>(telemetry => telemetryReceived.SetResult(telemetry));
            },
                      new LanguageServer.Server.CreationOptions(NamespaceProvider: BicepTestConstants.NamespaceProvider, FileResolver: new InMemoryFileResolver(fileSystemDict)));

            var client = helper.Client;

            var mainUri = DocumentUri.FromFileSystemPath("/main.bicep");

            fileSystemDict[mainUri.ToUri()] = text;

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(mainUri, fileSystemDict[mainUri.ToUri()], 1));

            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = new TextDocumentIdentifier(mainUri),
                Position     = position,
            });

            CompletionItem      completionItem = completions.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == prefix).First();
            Command?            command        = completionItem.Command;
            JArray?             arguments      = command !.Arguments;
            BicepTelemetryEvent?telemetryEvent = arguments !.First().ToObject <BicepTelemetryEvent>();

            await client.ResolveCompletion(completionItem);

            await client.Workspace.ExecuteCommand(command);

            return(await IntegrationTestHelper.WithTimeoutAsync(telemetryReceived.Task));
        }
Example #20
0
        public static async Task <LanguageServerHelper> StartServerWithTextAsync(TestContext testContext, string text, DocumentUri documentUri, Action <LanguageClientOptions>?onClientOptions = null, Server.CreationOptions?creationOptions = null)
        {
            var diagnosticsPublished = new TaskCompletionSource <PublishDiagnosticsParams>();

            creationOptions ??= new Server.CreationOptions();
            creationOptions = creationOptions with
            {
                FileResolver = creationOptions.FileResolver ?? new InMemoryFileResolver(new Dictionary <Uri, string> {
                    [documentUri.ToUri()] = text,
                }),
                ModuleRestoreScheduler = creationOptions.ModuleRestoreScheduler ?? BicepTestConstants.ModuleRestoreScheduler
            };
            var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                testContext,
                options =>
            {
                onClientOptions?.Invoke(options);
                options.OnPublishDiagnostics(p =>
                {
                    testContext.WriteLine($"Received {p.Diagnostics.Count()} diagnostic(s).");
                    diagnosticsPublished.SetResult(p);
                });
            },
                creationOptions);

            // send open document notification
            helper.Client.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(documentUri, text, 0));

            testContext.WriteLine($"Opened file {documentUri}.");

            // notifications don't produce responses,
            // but our server should send us diagnostics when it receives the notification
            await IntegrationTestHelper.WithTimeoutAsync(diagnosticsPublished.Task);

            return(helper);
        }
        public static async Task <MultiFileLanguageServerHelper> StartLanguageServer(TestContext testContext, Server.CreationOptions?creationOptions = null)
        {
            var notificationRouter = new ConcurrentDictionary <DocumentUri, TaskCompletionSource <PublishDiagnosticsParams> >();
            var helper             = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                testContext,
                onClientOptions : options =>
            {
                options.OnPublishDiagnostics(p =>
                {
                    testContext.WriteLine($"Received {p.Diagnostics.Count()} diagnostic(s).");

                    if (notificationRouter.TryGetValue(p.Uri, out var completionSource))
                    {
                        completionSource.SetResult(p);
                        return;
                    }

                    throw new AssertFailedException($"Task completion source was not registered for document uri '{p.Uri}'.");
                });
            },
                creationOptions : creationOptions);

            return(new(helper.Server, helper.Client, notificationRouter));
        }
Example #22
0
        public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri, creationOptions : new LanguageServer.Server.CreationOptions(NamespaceProvider: BicepTestConstants.NamespaceProvider, FileResolver: BicepTestConstants.FileResolver));

            var client = helper.Client;

            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SourceFileGrouping.EntryPoint.LineStarts;

            var symbolReferences = SyntaxAggregator.Aggregate(
                compilation.SourceFileGrouping.EntryPoint.ProgramSyntax,
                new List <SyntaxBase>(),
                (accumulated, node) =>
            {
                if (node is ISymbolReference || node is ITopLevelNamedDeclarationSyntax)
                {
                    accumulated.Add(node);
                }

                return(accumulated);
            },
                accumulated => accumulated);

            foreach (var symbolReference in symbolReferences)
            {
                // by default, request a hover on the first character of the syntax, but for certain syntaxes, this doesn't make sense.
                // for example on an instance function call 'az.resourceGroup()', it only makes sense to request a hover on the 3rd character.
                var nodeForHover = symbolReference switch
                {
                    ITopLevelDeclarationSyntax d => d.Keyword,
                    ResourceAccessSyntax r => r.ResourceName,
                    FunctionCallSyntaxBase f => f.Name,
                         _ => symbolReference,
                };

                var hover = await client.RequestHover(new HoverParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = TextCoordinateConverter.GetPosition(lineStarts, nodeForHover.Span.Position)
                });

                // fancy method to give us some annotated source code to look at if any assertions fail :)
                using (new AssertionScope().WithVisualCursor(compilation.SourceFileGrouping.EntryPoint, nodeForHover.Span.ToZeroLengthSpan()))
                {
                    if (!symbolTable.TryGetValue(symbolReference, out var symbol))
                    {
                        if (symbolReference is InstanceFunctionCallSyntax &&
                            compilation.GetEntrypointSemanticModel().GetSymbolInfo(symbolReference) is FunctionSymbol ifcSymbol)
                        {
                            ValidateHover(hover, ifcSymbol);
                            break;
                        }

                        // symbol ref not bound to a symbol
                        hover.Should().BeNull();
                        continue;
                    }

                    switch (symbol !.Kind)
                    {
        public async Task DidOpenTextDocument_should_trigger_PublishDiagnostics()
        {
            var documentUri   = DocumentUri.From("/template.bicep");
            var diagsReceived = new TaskCompletionSource <PublishDiagnosticsParams>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(this.TestContext, options =>
            {
                options.OnPublishDiagnostics(diags =>
                {
                    diagsReceived.SetResult(diags);
                });
            });

            var client = helper.Client;

            // open document
            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(documentUri, @"
param myParam string = 2
resource myRes 'invalidFormat' = {

}
randomToken
", 1));

            var response = await IntegrationTestHelper.WithTimeoutAsync(diagsReceived.Task);

            response.Diagnostics.Should().SatisfyRespectively(
                d =>
            {
                d.Range.Should().HaveRange((1, 6), (1, 13));
                // note documentation pretty printing moves Uri to code for output
                d.Should().HaveCodeAndSeverity(new NoUnusedParametersRule().Uri !.AbsoluteUri, DiagnosticSeverity.Warning);
            },
                d =>
            {
                d.Range.Should().HaveRange((1, 23), (1, 24));
                d.Should().HaveCodeAndSeverity("BCP027", DiagnosticSeverity.Error);
            },
                d =>
            {
                d.Range.Should().HaveRange((2, 15), (2, 30));
                d.Should().HaveCodeAndSeverity("BCP029", DiagnosticSeverity.Error);
            },
                d =>
            {
                d.Range.Should().HaveRange((5, 0), (5, 11));
                d.Should().HaveCodeAndSeverity("BCP007", DiagnosticSeverity.Error);
            }
                );

            // change document
            diagsReceived = new TaskCompletionSource <PublishDiagnosticsParams>();
            client.TextDocument.DidChangeTextDocument(TextDocumentParamHelper.CreateDidChangeTextDocumentParams(documentUri, @"
param myParam string = 'fixed!'
resource myRes 'invalidFormat' = {

}
randomToken
", 2));

            response = await IntegrationTestHelper.WithTimeoutAsync(diagsReceived.Task);

            response.Diagnostics.Should().SatisfyRespectively(
                d =>
            {
                d.Range.Should().HaveRange((1, 6), (1, 13));
                // documentation provided with linter sets code to uri for pretty link print outs
                d.Should().HaveCodeAndSeverity(new NoUnusedParametersRule().Uri !.AbsoluteUri, DiagnosticSeverity.Warning);
            },
                d =>
            {
                d.Range.Should().HaveRange((2, 15), (2, 30));
                d.Should().HaveCodeAndSeverity("BCP029", DiagnosticSeverity.Error);
            },
                d =>
            {
                d.Range.Should().HaveRange((5, 0), (5, 11));
                d.Should().HaveCodeAndSeverity("BCP007", DiagnosticSeverity.Error);
            }
                );

            // close document
            diagsReceived = new TaskCompletionSource <PublishDiagnosticsParams>();
            client.TextDocument.DidCloseTextDocument(TextDocumentParamHelper.CreateDidCloseTextDocumentParams(documentUri, 3));

            response = await IntegrationTestHelper.WithTimeoutAsync(diagsReceived.Task);

            response.Diagnostics.Should().BeEmpty();
        }
Example #24
0
        public async Task BicepFileOpen_ShouldFireTelemetryEvent()
        {
            var testOutputPath = Path.Combine(TestContext.ResultsDirectory, Guid.NewGuid().ToString());

            var bicepConfigFileContents = @"{
  ""analyzers"": {
    ""core"": {
      ""verbose"": false,
      ""enabled"": true,
      ""rules"": {
        ""no-unused-params"": {
          ""level"": ""warning""
        },
        ""no-unused-vars"": {
          ""level"": ""info""
        }
      }
    }
  }
}";

            FileHelper.SaveResultFile(TestContext, "bicepconfig.json", bicepConfigFileContents, testOutputPath);

            var fileSystemDict = new Dictionary <Uri, string>();

            var mainBicepFileContents = @"param appInsightsName string = 'testAppInsightsName'

var deployGroups = true

resource applicationInsights 'Microsoft.Insights/components@2015-05-01' = {
  name: appInsightsName
  location: resourceGroup().location
  kind: 'web'
  properties: {
    Application_Type: 'web'
  }
  resource favorites 'favorites@2015-05-01'{
    name: 'testName'
  }
}

module apimGroups 'groups.bicep' = if (deployGroups) {
  name: 'apimGroups'
}

param location string = 'testLocation'

#disable-next-line";
            var mainBicepFilePath     = FileHelper.SaveResultFile(TestContext, "main.bicep", mainBicepFileContents, testOutputPath);
            var mainUri = DocumentUri.FromFileSystemPath(mainBicepFilePath);

            fileSystemDict[mainUri.ToUri()] = mainBicepFileContents;

            var referencedBicepFileContents = @"resource parentAPIM 'Microsoft.ApiManagement/service@2019-01-01' existing = {
  name: 'testApimInstanceName'
}

resource apimGroup 'Microsoft.ApiManagement/service/groups@2020-06-01-preview' = {
  name: 'apiManagement/groups'
}

param storageAccount string = 'testStorageAccount'
param location string = 'testLocation'

var useDefaultSettings = true";
            var referencedBicepFilePath     = FileHelper.SaveResultFile(TestContext, "groups.bicep", referencedBicepFileContents, testOutputPath);
            var moduleUri = DocumentUri.FromFileSystemPath(referencedBicepFilePath);

            fileSystemDict[moduleUri.ToUri()] = referencedBicepFileContents;

            var telemetryEventsListener = new MultipleMessageListener <BicepTelemetryEvent>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      TestContext,
                      options =>
            {
                options.OnTelemetryEvent <BicepTelemetryEvent>(telemetry => telemetryEventsListener.AddMessage(telemetry));
            },
                      creationOptions : new Server.CreationOptions(FileResolver: new InMemoryFileResolver(fileSystemDict)));

            var client = helper.Client;

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(mainUri, fileSystemDict[mainUri.ToUri()], 1));

            var bicepTelemetryEvent = await telemetryEventsListener.WaitNext();

            IDictionary <string, string> properties = new Dictionary <string, string>
            {
                { "enabled", "true" },
                { "simplify-interpolation", "warning" },
                { "no-unused-vars", "info" },
                { "no-hardcoded-env-urls", "warning" },
                { "no-unused-params", "warning" },
                { "prefer-interpolation", "warning" },
                { "protect-commandtoexecute-secrets", "warning" },
                { "no-unnecessary-dependson", "warning" },
                { "adminusername-should-not-be-literal", "warning" },
                { "use-stable-vm-image", "warning" },
                { "secure-parameter-default", "warning" },
                { "outputs-should-not-contain-secrets", "warning" },
            };

            bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.LinterRuleStateOnBicepFileOpen);
            bicepTelemetryEvent.Properties.Should().Contain(properties);

            properties = new Dictionary <string, string>
            {
                { "modules", "1" },
                { "parameters", "2" },
                { "resources", "2" },
                { "variables", "1" },
                { "fileSizeInBytes", "488" },
                { "lineCount", "23" },
                { "errors", "2" },
                { "warnings", "1" },
                { "modulesInReferencedFiles", "0" },
                { "parentResourcesInReferencedFiles", "2" },
                { "parametersInReferencedFiles", "2" },
                { "variablesInReferencedFiles", "1" }
            };

            bicepTelemetryEvent = await telemetryEventsListener.WaitNext();

            bicepTelemetryEvent.EventName.Should().Be(TelemetryConstants.EventNames.BicepFileOpen);
            bicepTelemetryEvent.Properties.Should().Contain(properties);
        }
Example #25
0
        public async Task RequestDeploymentGraphShouldReturnDeploymentGraph()
        {
            var diagnosticsListener = new MultipleMessageListener <PublishDiagnosticsParams>();
            var fileSystemDict      = new Dictionary <Uri, string>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(
                      this.TestContext,
                      options => options.OnPublishDiagnostics(diagnosticsParams => diagnosticsListener.AddMessage(diagnosticsParams)),
                      new LanguageServer.Server.CreationOptions(NamespaceProvider: BuiltInTestTypes.Create(), FileResolver: new InMemoryFileResolver(fileSystemDict)));

            var client = helper.Client;

            var mainUri = DocumentUri.FromFileSystemPath("/main.bicep");

            fileSystemDict[mainUri.ToUri()] = @"
resource res1 'Test.Rp/basicTests@2020-01-01' = {
  name: 'res1'
}

resource res2 'Test.Rp/readWriteTests@2020-01-01' = {
  name: 'res2'
  properites: {
    readwrite: mod1.outputs.output1
  }
}

resource unknownRes = {
}

module mod1 './modules/module1.bicep' = {
  name: 'mod1'
}

module mod2 './modules/module2.bicep' = {
  name: 'mod2'
}

module nonExistingMod './path/to/nonExistingModule.bicep' = {
}
";

            var module1Uri = DocumentUri.FromFileSystemPath("/modules/module1.bicep");

            fileSystemDict[module1Uri.ToUri()] = @"
resource res3 'Test.Rp/basicTests@2020-01-01' = {
  name: 'res3'
}

output output1 int = 123
";

            var module2Uri = DocumentUri.FromFileSystemPath("/modules/module2.bicep");

            fileSystemDict[module2Uri.ToUri()] = @"
resource res4 'Test.Rp/basicTests@2020-01-01' = {
  name: 'res4'
}

module nestedMod './nestedModules/nestedModule.bicep' = [for x in []: {
  name: 'nestedMod'
  dependsOn: [
    res4
  ]
}]
";

            var nestedModuleUri = DocumentUri.FromFileSystemPath("/modules/nestedModules/nestedModule.bicep");

            fileSystemDict[nestedModuleUri.ToUri()] = @"
resource res5 'Test.Rp/basicTests@2020-01-01' = {
  name: 'res5'
}
";

            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(mainUri, fileSystemDict[mainUri.ToUri()], 1));
            await diagnosticsListener.WaitNext();

            var deploymentGraph = await client.SendRequest(new BicepDeploymentGraphParams(new TextDocumentIdentifier(mainUri)), default);

            deploymentGraph.Should().NotBeNull();
            deploymentGraph !.Nodes.Should().Equal(
                new BicepDeploymentGraphNode("mod1", "<module>", false, CreateTextRange(15, 0, 17, 1), true, false, Path.GetFullPath(mainUri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("mod1::res3", "Test.Rp/basicTests", false, CreateTextRange(1, 0, 3, 1), false, false, Path.GetFullPath(module1Uri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("mod2", "<module>", false, CreateTextRange(19, 0, 21, 1), true, false, Path.GetFullPath(mainUri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("mod2::nestedMod", "<module>", true, CreateTextRange(5, 0, 10, 2), true, false, Path.GetFullPath(module2Uri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("mod2::nestedMod::res5", "Test.Rp/basicTests", false, CreateTextRange(1, 0, 3, 1), false, false, Path.GetFullPath(nestedModuleUri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("mod2::res4", "Test.Rp/basicTests", false, CreateTextRange(1, 0, 3, 1), false, false, Path.GetFullPath(module2Uri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("nonExistingMod", "<module>", false, CreateTextRange(23, 0, 24, 1), false, true, Path.GetFullPath(mainUri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("res1", "Test.Rp/basicTests", false, CreateTextRange(1, 0, 3, 1), false, false, Path.GetFullPath(mainUri.GetFileSystemPath())),
                new BicepDeploymentGraphNode("res2", "Test.Rp/readWriteTests", false, CreateTextRange(5, 0, 10, 1), false, true, Path.GetFullPath(mainUri.GetFileSystemPath())));
            deploymentGraph !.Edges.Should().Equal(
                new BicepDeploymentGraphEdge("mod2::nestedMod", "mod2::res4"),
                new BicepDeploymentGraphEdge("res2", "mod1"));
            deploymentGraph !.ErrorCount.Should().Be(7);
        }
        public async Task RequestDocumentSymbol_should_return_full_symbol_list()
        {
            var documentUri   = DocumentUri.From("/template.bicep");
            var diagsReceived = new TaskCompletionSource <PublishDiagnosticsParams>();

            using var helper = await LanguageServerHelper.StartServerWithClientConnectionAsync(this.TestContext, options =>
            {
                options.OnPublishDiagnostics(diags =>
                {
                    diagsReceived.SetResult(diags);
                });
            });

            var client = helper.Client;

            // client opens the document
            client.TextDocument.DidOpenTextDocument(TextDocumentParamHelper.CreateDidOpenDocumentParams(documentUri, @"
param myParam string = 'test'
resource myRes 'myRp/provider@2019-01-01' = {
  name: 'test'
}
module myMod './module.bicep' = {
  name: 'test' 
}
output myOutput string = 'myOutput'
", 0));

            // client requests symbols
            var symbols = await client.TextDocument.RequestDocumentSymbol(new DocumentSymbolParams
            {
                TextDocument = new TextDocumentIdentifier
                {
                    Uri = documentUri,
                },
            });

            symbols.Should().SatisfyRespectively(
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myParam");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Field);
            },
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myRes");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Object);
            },
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myMod");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Module);
            },
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myOutput");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Interface);
            }
                );

            // client deletes the output and renames the resource
            client.TextDocument.DidChangeTextDocument(TextDocumentParamHelper.CreateDidChangeTextDocumentParams(documentUri, @"
param myParam string = 'test'
resource myRenamedRes 'myRp/provider@2019-01-01' = {
  name: 'test'
}
module myMod './module.bicep' = {
  name: 'test'
}
", 1));

            // client requests symbols
            symbols = await client.TextDocument.RequestDocumentSymbol(new DocumentSymbolParams
            {
                TextDocument = new TextDocumentIdentifier
                {
                    Uri = documentUri,
                },
            });

            symbols.Should().SatisfyRespectively(
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myParam");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Field);
            },
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myRenamedRes");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Object);
            },
                x =>
            {
                x.DocumentSymbol !.Name.Should().Be("myMod");
                x.DocumentSymbol.Kind.Should().Be(SymbolKind.Module);
            }
                );
        }