Exemplo n.º 1
0
        public async Task Property_completions_include_descriptions()
        {
            var(file, cursors) = ParserHelper.GetFileWithCursors(@"
resource testRes 'Test.Rp/readWriteTests@2020-01-01' = {
  name: 'testRes'
  properties: {
    |
  }
}

output string test = testRes.|
output string test2 = testRes.properties.|
");

            var syntaxTree = SyntaxTree.Create(new Uri("file:///path/to/main.bicep"), file);
            var client     = await IntegrationTestHelper.StartServerWithTextAsync(file, syntaxTree.FileUri, resourceTypeProvider : BuiltInTestTypes.Create());

            var completions = await RequestCompletions(client, syntaxTree, cursors);

            completions.Should().SatisfyRespectively(
                x => x !.OrderBy(d => d.SortText).Should().SatisfyRespectively(
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("This is a property which supports reading AND writing!"),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("This is a property which is required."),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("This is a property which only supports writing.")),
                x => x !.OrderBy(d => d.SortText).Should().SatisfyRespectively(
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("apiVersion property"),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("id property"),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("name property"),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("properties property"),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("type property")),
                x => x !.OrderBy(d => d.SortText).Should().SatisfyRespectively(
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("This is a property which only supports reading."),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("This is a property which supports reading AND writing!"),
                    d => d.Documentation !.MarkupContent !.Value.Should().Contain("This is a property which is required.")));
        }
Exemplo n.º 2
0
        public async Task HighlightsShouldShowAllReferencesOfTheSymbol(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateFromText(dataSet.Bicep));
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = TextCoordinateConverter.GetLineStarts(dataSet.Bicep);

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

            foreach (var(syntax, symbol) in symbolTable.Where(s => s.Value.Kind != SymbolKind.Error))
            {
                var highlights = await client.RequestDocumentHighlight(new DocumentHighlightParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = PositionHelper.GetPosition(lineStarts, syntax.Span.Position)
                });

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

                // ranges should match what we got from our own symbol table
                highlights.Should().BeEquivalentTo(expectedHighlights);
            }
        }
Exemplo n.º 3
0
        public async Task String_segments_do_not_return_completions()
        {
            var(file, cursors) = ParserHelper.GetFileWithCursors(@"
var completeString = |'he|llo'|
var interpolatedString = |'abc${|true}|de|f${|false}|gh|i'|
var multilineString = |'''|
hel|lo
'''|
");

            var syntaxTree = SyntaxTree.Create(new Uri("file:///main.bicep"), file);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(file, syntaxTree.FileUri, resourceTypeProvider : TypeProvider);

            foreach (var cursor in cursors)
            {
                using (new AssertionScope().WithVisualCursor(syntaxTree, new TextSpan(cursor, 0)))
                {
                    var completions = await client.RequestCompletion(new CompletionParams
                    {
                        TextDocument = new TextDocumentIdentifier(syntaxTree.FileUri),
                        Position     = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, cursor),
                    });

                    completions.Should().BeEmpty();
                }
            }
        }
Exemplo n.º 4
0
        public async Task Completions_are_not_offered_inside_comments()
        {
            var(file, cursors) = ParserHelper.GetFileWithCursors(@"
var test = /|/ comment here|
var test2 = /|* block c|omment *|/
");

            var syntaxTree = SyntaxTree.Create(new Uri("file:///main.bicep"), file);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(file, syntaxTree.FileUri, resourceTypeProvider : TypeProvider);

            foreach (var cursor in cursors)
            {
                using (new AssertionScope().WithVisualCursor(syntaxTree, new TextSpan(cursor, 0)))
                {
                    var completions = await client.RequestCompletion(new CompletionParams
                    {
                        TextDocument = new TextDocumentIdentifier(syntaxTree.FileUri),
                        Position     = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, cursor),
                    });

                    completions.Should().BeEmpty();
                }
            }
        }
Exemplo n.º 5
0
        public async Task RenamingFunctionsShouldProduceEmptyEdit(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SyntaxTreeGrouping.EntryPoint.LineStarts;

            var validFunctionCallPairs = symbolTable
                                         .Where(pair => pair.Value.Kind == SymbolKind.Function)
                                         .Select(pair => pair.Key);

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

                edit.DocumentChanges.Should().BeNullOrEmpty();
                edit.Changes.Should().BeNull();
            }
        }
Exemplo n.º 6
0
        private async Task <string> RequestSnippetCompletion(string bicepFileName, CompletionData completionData, string placeholderFile)
        {
            var documentUri = DocumentUri.FromFileSystemPath(bicepFileName);
            var syntaxTree  = SyntaxTree.Create(documentUri.ToUri(), placeholderFile);

            var client = await IntegrationTestHelper.StartServerWithTextAsync(
                placeholderFile,
                documentUri,
                null,
                TypeProvider);

            var cursor      = placeholderFile.IndexOf("// Insert snippet here");
            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = documentUri,
                Position     = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, cursor),
            });

            var matchingSnippets = completions.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == completionData.Prefix);

            matchingSnippets.Should().HaveCount(1);
            var completion = matchingSnippets.First();

            completion.TextEdit.Should().NotBeNull();
            completion.TextEdit !.Range.Should().Be(new TextSpan(cursor, 0).ToRange(syntaxTree.LineStarts));
            completion.TextEdit.NewText.Should().NotBeNullOrWhiteSpace();

            return(completion.TextEdit.NewText);
        }
Exemplo n.º 7
0
        public async Task NonFunctionCallSyntaxShouldProvideNoSignatureHelp(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var tree        = compilation.SyntaxTreeGrouping.EntryPoint;

            var nonFunctions = SyntaxAggregator.Aggregate(
                tree.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)
            {
                var position      = PositionHelper.GetPosition(tree.LineStarts, nonFunction.Span.Position);
                var signatureHelp = await RequestSignatureHelp(client, position, uri);

                signatureHelp.Should().BeNull();
            }
        }
Exemplo n.º 8
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 client = await IntegrationTestHelper.StartServerWithTextAsync(TestContext, dataSet.Bicep, uri);

            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();
                    }
                }
            }
        }
Exemplo n.º 9
0
        public async Task GoToDefinitionRequestOnUnsupportedOrInvalidSyntaxNodeShouldReturnEmptyResponse(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SyntaxTreeGrouping.EntryPoint.LineStarts;

            var undeclaredSymbolBindings = symbolTable.Where(pair => !(pair.Value is DeclaredSymbol));

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

                // 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();
            }
        }
Exemplo n.º 10
0
        public async Task GoToDefinitionRequestOnUnsupportedOrInvalidSyntaxNodeShouldReturnEmptyResponse(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            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();
                }
            }
        }
Exemplo n.º 11
0
        public async Task RenamingFunctionsShouldProduceEmptyEdit(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateFromText(dataSet.Bicep));
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = TextCoordinateConverter.GetLineStarts(dataSet.Bicep);

            var validFunctionCallPairs = symbolTable
                                         .Where(pair => pair.Value.Kind == SymbolKind.Function)
                                         .Select(pair => pair.Key);

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

                edit.DocumentChanges.Should().BeNullOrEmpty();
                edit.Changes.Should().BeNull();
            }
        }
Exemplo n.º 12
0
        public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers(DataSet dataSet)
        {
            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _, out var fileUri);
            var uri         = DocumentUri.From(fileUri);
            var client      = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri, resourceTypeProvider : AzResourceTypeProvider.CreateWithAzTypes(), fileResolver : BicepTestConstants.FileResolver);

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

            var symbolReferences = SyntaxAggregator.Aggregate(
                compilation.SyntaxTreeGrouping.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.SyntaxTreeGrouping.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)
                    {
Exemplo n.º 13
0
        public async Task HighlightsShouldShowAllReferencesOfTheSymbol(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SyntaxTreeGrouping.EntryPoint.LineStarts;

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

            foreach (var(syntax, symbol) in symbolTable.Where(s => s.Value.Kind != SymbolKind.Error))
            {
                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));

                // ranges should match what we got from our own symbol table
                highlights.Should().BeEquivalentTo(expectedHighlights);
            }
        }
Exemplo n.º 14
0
        public async Task VerifyNestedResourceCompletionReturnsCustomSnippetWithoutParentInformation()
        {
            string fileWithCursors = @"resource automationAccount 'Microsoft.Automation/automationAccounts@2019-06-01' = {
  name: 'name'
  location: resourceGroup().location

  |
}";

            var(file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
            var syntaxTree = SyntaxTree.Create(new Uri("file:///path/to/main.bicep"), file);
            var client     = await IntegrationTestHelper.StartServerWithTextAsync(TestContext, file, syntaxTree.FileUri, resourceTypeProvider : TypeProvider);

            var completionLists = await RequestCompletions(client, syntaxTree, cursors);

            completionLists.Count().Should().Be(1);

            var snippetCompletion = completionLists.First() !.Items.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == "res-automation-cred");

            snippetCompletion.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("res-automation-cred");
                c.Detail.Should().Be("Automation Credential");
                c.InsertTextFormat.Should().Be(InsertTextFormat.Snippet);
                c.TextEdit?.TextEdit?.NewText?.Should().BeEquivalentToIgnoringNewlines(@"resource ${2:automationCredential} 'credentials@2019-06-01' = {
  name: ${3:'name'}
  properties: {
    userName: ${4:'userName'}
    password: ${5:'password'}
    description: ${6:'description'}
  }
}");
            });
        }
Exemplo n.º 15
0
        public async Task HighlightsShouldShowAllReferencesOfTheSymbol(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SyntaxTreeGrouping.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));

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

                // ranges should match what we got from our own symbol table
                highlights.Should().BeEquivalentTo(expectedHighlights);
            }
        }
Exemplo n.º 16
0
        public async Task NonExistentUriShouldProvideNoSignatureHelp()
        {
            using var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, string.Empty, DocumentUri.From("/fake.bicep"));

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

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

            var uri = DocumentUri.From(fileUri);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            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));
                }
            }
        }
Exemplo n.º 18
0
        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);

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

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

            assertAction(results);
        }
Exemplo n.º 19
0
        public async Task RequestingCodeActionWithFixableDiagnosticsShouldProduceQuickFixes(DataSet dataSet)
        {
            var uri    = DocumentUri.From($"/{dataSet.Name}");
            var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            // construct a parallel compilation
            var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateFromText(dataSet.Bicep));
            var lineStarts  = TextCoordinateConverter.GetLineStarts(dataSet.Bicep);
            var fixables    = compilation.GetSemanticModel().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.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();
                        var replacementList = bicepFix.Replacements.ToList();

                        for (int j = 0; j < textEditList.Count; j++)
                        {
                            var textEdit    = textEditList[j];
                            var replacement = replacementList[j];

                            textEdit.Range.Should().Be(replacement.ToRange(lineStarts));
                            textEdit.NewText.Should().Be(replacement.Text);
                        }
                    }
                }
            }
        }
Exemplo n.º 20
0
        public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers(DataSet dataSet)
        {
            var uri    = DocumentUri.From($"/{dataSet.Name}");
            var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri, resourceTypeProvider : new AzResourceTypeProvider(new TypeLoader()));

            // construct a parallel compilation
            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);

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

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

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

            foreach (SyntaxBase symbolReference in symbolReferences)
            {
                var nodeForHover = symbolReference switch
                {
                    ITopLevelDeclarationSyntax d => d.Keyword,
                    ResourceAccessSyntax r => r.ResourceName,
                                _ => symbolReference,
                };

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

                // fancy method to give us some annotated source code to look at if any assertions fail :)
                using (CreateAssertionScopeWithContext(compilation.SyntaxTreeGrouping.EntryPoint, hover, nodeForHover.Span.ToZeroLengthSpan()))
                {
                    if (symbolTable.TryGetValue(symbolReference, out var symbol) == false)
                    {
                        // symbol ref not bound to a symbol
                        hover.Should().BeNull();
                        continue;
                    }

                    switch (symbol !.Kind)
                    {
Exemplo n.º 21
0
        public async Task GoToDefinitionOnUnboundSyntaxNodeShouldReturnEmptyResponse(DataSet dataSet)
        {
            // local function
            bool IsUnboundNode(IDictionary <SyntaxBase, Symbol> dictionary, SyntaxBase syntax) => dictionary.ContainsKey(syntax) == false && !(syntax is Token);

            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SyntaxTreeGrouping.EntryPoint.LineStarts;

            var unboundNodes = SyntaxAggregator.Aggregate(
                source: compilation.SyntaxTreeGrouping.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));

            foreach (var syntax in unboundNodes)
            {
                var offset = syntax switch
                {
                    // base expression could be a variable access which is bound and will throw off the test
                    PropertyAccessSyntax propertyAccess => propertyAccess.PropertyName.Span.Position,
                    ArrayAccessSyntax arrayAccess => arrayAccess.OpenSquare.Span.Position,

                         _ => syntax.Span.Position
                };

                var response = await client.RequestDefinition(new DefinitionParams
                {
                    TextDocument = new TextDocumentIdentifier(uri),
                    Position     = PositionHelper.GetPosition(lineStarts, offset)
                });

                // go to definition on a syntax node that isn't bound to a symbol should produce an empty response
                response.Should().BeEmpty();
            }
        }
Exemplo n.º 22
0
        public async Task VerifyNestedResourceBodyCompletionReturnsSnippets()
        {
            string fileWithCursors = @"resource automationAccount 'Microsoft.Automation/automationAccounts@2019-06-01' = {
  name: 'name'
  location: resourceGroup().location

  resource automationCredential 'credentials@2019-06-01' = |
}";

            var(file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
            var syntaxTree = SyntaxTree.Create(new Uri("file:///path/to/main.bicep"), file);
            var client     = await IntegrationTestHelper.StartServerWithTextAsync(TestContext, file, syntaxTree.FileUri, resourceTypeProvider : TypeProvider);

            var completionLists = await RequestCompletions(client, syntaxTree, cursors);

            completionLists.Count().Should().Be(1);

            var snippetCompletions = completionLists.First() !.Items.Where(x => x.Kind == CompletionItemKind.Snippet);

            snippetCompletions.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("{}");
            },
                c =>
            {
                c.Label.Should().Be("snippet");
            },
                c =>
            {
                c.Label.Should().Be("required-properties");
            },
                c =>
            {
                c.Label.Should().Be("if");
            },
                c =>
            {
                c.Label.Should().Be("for");
            },
                c =>
            {
                c.Label.Should().Be("for-indexed");
            },
                c =>
            {
                c.Label.Should().Be("for-filtered");
            });
        }
Exemplo n.º 23
0
        public async Task FindReferencesWithoutDeclarationsShouldProduceCorrectResults(DataSet dataSet)
        {
            var(compilation, _, fileUri) = await dataSet.SetupPrerequisitesAndCreateCompilation(TestContext);

            var uri = DocumentUri.From(fileUri);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            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);
                }
            }
        }
Exemplo n.º 24
0
        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 client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri);

            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();
            }
        }
Exemplo n.º 25
0
        public async Task RenamingIdentifierAccessOrDeclarationShouldRenameDeclarationAndAllReferences(DataSet dataSet)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateFromText(dataSet.Bicep));
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = TextCoordinateConverter.GetLineStarts(dataSet.Bicep);

            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 IDeclarationSyntax) &&
                                                  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.DocumentChanges.Should().BeNullOrEmpty();
                edit.Changes.Should().HaveCount(1);
                edit.Changes.Should().ContainKey(uri);

                var textEdits = edit.Changes[uri];
                textEdits.Should().NotBeEmpty();

                var expectedEdits = symbolToSyntaxLookup[symbol]
                                    .Select(node => CreateExpectedTextEdit(lineStarts, expectedNewText, node));

                textEdits.Should().BeEquivalentTo(expectedEdits);
            }
        }
Exemplo n.º 26
0
        public async Task CompletionRequestShouldProduceExpectedCompletions(DataSet dataSet, string setName, IList <Position> positions)
        {
            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var intermediate = new List <(Position position, JToken actual)>();

            foreach (var position in positions)
            {
                var actual = await GetActualCompletions(client, uri, position);

                intermediate.Add((position, actual));
            }

            ValidateCompletions(dataSet, setName, intermediate);
        }
Exemplo n.º 27
0
        public async Task RenamingNonSymbolsShouldProduceEmptyEdit(DataSet dataSet)
        {
            // local function
            bool IsWrongNode(SyntaxBase node) => !(node is ISymbolReference) && !(node is IDeclarationSyntax) && !(node is Token);

            var uri = DocumentUri.From($"/{dataSet.Name}");

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateFromText(dataSet.Bicep));
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = TextCoordinateConverter.GetLineStarts(dataSet.Bicep);

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

            var wrongNodes = SyntaxAggregator.Aggregate(
                compilation.ProgramSyntax,
                new List <SyntaxBase>(),
                (accumulated, node) =>
            {
                if (IsWrongNode(node) && !(node is ProgramSyntax))
                {
                    accumulated.Add(node);
                }

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

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

                edit.DocumentChanges.Should().BeNullOrEmpty();
                edit.Changes.Should().BeNull();
            }
        }
Exemplo n.º 28
0
        public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers(DataSet dataSet)
        {
            var uri    = DocumentUri.From($"/{dataSet.Name}");
            var client = await IntegrationTestHelper.StartServerWithTextAsync(dataSet.Bicep, uri);

            // construct a parallel compilation
            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var symbolTable = compilation.ReconstructSymbolTable();
            var lineStarts  = compilation.SyntaxTreeGrouping.EntryPoint.LineStarts;

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

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

            foreach (SyntaxBase symbolReference in symbolReferences)
            {
                var syntaxPosition = symbolReference is IDeclarationSyntax declaration
                    ? declaration.Keyword.Span.Position
                    : symbolReference.Span.Position;

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

                if (symbolTable.TryGetValue(symbolReference, out var symbol) == false)
                {
                    // symbol ref not bound to a symbol
                    hover.Should().BeNull();
                    continue;
                }

                switch (symbol !.Kind)
                {
Exemplo n.º 29
0
        public async Task RequestingCodeActionWithFixableDiagnosticsShouldProduceQuickFixes(DataSet dataSet)
        {
            // ensure all files (e.g. modules) are present locally
            string basePath   = dataSet.SaveFilesToTestDirectory(this.TestContext);
            var    entryPoint = Path.Combine(basePath, "main.bicep");
            var    uri        = DocumentUri.FromFileSystemPath(entryPoint);

            // start language server
            var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, dataSet.Bicep, uri, fileResolver : new FileResolver());

            // construct a parallel compilation
            var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out _);
            var lineStarts  = compilation.SyntaxTreeGrouping.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.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();
Exemplo n.º 30
0
        public async Task VerifyResourceBodyCompletionWithoutExistingKeywordIncludesCustomSnippet()
        {
            string text = @"resource aksCluster 'Microsoft.ContainerService/managedClusters@2021-03-01' = ";

            var syntaxTree = SyntaxTree.Create(new Uri("file:///main.bicep"), text);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(text, syntaxTree.FileUri, resourceTypeProvider : TypeProvider);

            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = new TextDocumentIdentifier(syntaxTree.FileUri),
                Position     = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, text.Length),
            });

            completions.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("{}");
            },
                c =>
            {
                c.Label.Should().Be("snippet");
            },
                c =>
            {
                c.Label.Should().Be("required-properties");
            },
                c =>
            {
                c.Label.Should().Be("if");
            },
                c =>
            {
                c.Label.Should().Be("for");
            },
                c =>
            {
                c.Label.Should().Be("for-indexed");
            },
                c =>
            {
                c.Label.Should().Be("for-filtered");
            });
        }