public void ProgramsShouldProduceExpectedUserDeclaredSymbols(DataSet dataSet) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var model = compilation.GetSemanticModel(); var symbols = SymbolCollector .CollectSymbols(model) .OfType <DeclaredSymbol>(); var lineStarts = TextCoordinateConverter.GetLineStarts(dataSet.Bicep); string getLoggingString(DeclaredSymbol symbol) { (_, var startChar) = TextCoordinateConverter.GetPosition(lineStarts, symbol.DeclaringSyntax.Span.Position); return($"{symbol.Kind} {symbol.Name}. Type: {symbol.Type}. Declaration start char: {startChar}, length: {symbol.DeclaringSyntax.Span.Length}"); } var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(dataSet, symbols, symb => symb.NameSyntax.Span, getLoggingString); var resultsFile = FileHelper.SaveResultFile(this.TestContext !, Path.Combine(dataSet.Name, DataSet.TestFileMainSymbols), sourceTextWithDiags); sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( dataSet.Symbols, expectedLocation: OutputHelper.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainSymbols), actualLocation: resultsFile); }
public void LockedModeShouldBlockAccess() { const string expectedMessage = "Properties of the symbol context should not be accessed until name binding is completed."; var bindings = new Dictionary <SyntaxBase, Symbol>(); var cyclesBySyntax = new Dictionary <SyntaxBase, ImmutableArray <DeclaredSymbol> >(); var context = new SymbolContext(new TypeManager(TestResourceTypeProvider.CreateRegistrar(), bindings, cyclesBySyntax), bindings); Action byName = () => { var tm = context.TypeManager; }; byName.Should().Throw <InvalidOperationException>().WithMessage(expectedMessage); Action byNode = () => { var b = context.Bindings; }; byNode.Should().Throw <InvalidOperationException>().WithMessage(expectedMessage); context.Unlock(); context.TypeManager.Should().NotBeNull(); context.Bindings.Should().NotBeNull(); }
public async Task GoToDefinitionRequestOnUnsupportedOrInvalidSyntaxNodeShouldReturnEmptyResponse(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); using var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var symbolTable = compilation.ReconstructSymbolTable(); var lineStarts = TextCoordinateConverter.GetLineStarts(dataSet.Bicep); 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(); } }
public async Task RenamingFunctionsShouldProduceEmptyEdit(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); using var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), 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 = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); edit.DocumentChanges.Should().BeNullOrEmpty(); edit.Changes.Should().BeNull(); } }
public async Task HighlightsShouldShowAllReferencesOfTheSymbol(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); using var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), 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); } }
public void EmptyProgram_SyntaxNodeShouldBePersisted() { var program = SyntaxFactory.CreateFromText(DataSets.Empty.Bicep); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), program); compilation.ProgramSyntax.Should().BeSameAs(program); compilation.GetSemanticModel().Should().NotBeNull(); }
private EmitResult EmitTemplate(string text, string filePath) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(text)); var emitter = new TemplateEmitter(compilation.GetSemanticModel()); using var stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None); return(emitter.Emit(stream)); }
private EmitResult EmitTemplate(string text, MemoryStream memoryStream) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(text)); var emitter = new TemplateEmitter(compilation.GetSemanticModel()); TextWriter tw = new StreamWriter(memoryStream); return(emitter.Emit(tw)); }
public void NonDeclarationContextShouldReturnDeclarationTypeCompletions() { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(string.Empty)); var provider = new BicepCompletionProvider(); var completions = provider.GetFilteredCompletions(compilation.GetSemanticModel(), new BicepCompletionContext(BicepCompletionContextKind.None)); var declarationTypeCompletions = completions.Where(c => c.Kind == CompletionItemKind.Class).ToList(); declarationTypeCompletions.Should().SatisfyRespectively( c => { const string expected = "array"; c.Label.Should().Be(expected); c.Kind.Should().Be(CompletionItemKind.Class); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be(expected); c.Detail.Should().Be(expected); }, c => { const string expected = "bool"; c.Label.Should().Be(expected); c.Kind.Should().Be(CompletionItemKind.Class); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be(expected); c.Detail.Should().Be(expected); }, c => { const string expected = "int"; c.Label.Should().Be(expected); c.Kind.Should().Be(CompletionItemKind.Class); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be(expected); c.Detail.Should().Be(expected); }, c => { const string expected = "object"; c.Label.Should().Be(expected); c.Kind.Should().Be(CompletionItemKind.Class); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be(expected); c.Detail.Should().Be(expected); }, c => { const string expected = "string"; c.Label.Should().Be(expected); c.Kind.Should().Be(CompletionItemKind.Class); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be(expected); c.Detail.Should().Be(expected); }); }
public void Create_ShouldReturnValidCompilation() { var provider = new BicepCompilationProvider(TestResourceTypeProvider.CreateRegistrar()); var context = provider.Create(DataSets.Parameters_LF.Bicep); context.Compilation.Should().NotBeNull(); context.Compilation.GetSemanticModel().GetAllDiagnostics().Should().BeEmpty(); context.LineStarts.Should().NotBeEmpty(); context.LineStarts[0].Should().Be(0); }
public void DeclarationContextShouldReturnKeywordCompletions() { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(string.Empty)); compilation.GetSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(); var completions = provider.GetFilteredCompletions(compilation.GetSemanticModel(), new BicepCompletionContext(BicepCompletionContextKind.Declaration)); var keywordCompletions = completions .Where(c => c.Kind == CompletionItemKind.Keyword) .OrderBy(c => c.Label) .ToList(); keywordCompletions.Should().SatisfyRespectively( c => { c.Label.Should().Be("output"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be("output"); c.CommitCharacters.Should().OnlyContain(s => string.Equals(s, " ")); c.Detail.Should().Be("Output keyword"); }, c => { c.Label.Should().Be("param"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be("param"); c.CommitCharacters.Should().OnlyContain(s => string.Equals(s, " ")); c.Detail.Should().Be("Parameter keyword"); }, c => { c.Label.Should().Be("resource"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be("resource"); c.CommitCharacters.Should().OnlyContain(s => string.Equals(s, " ")); c.Detail.Should().Be("Resource keyword"); }, c => { c.Label.Should().Be("var"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().Be("var"); c.CommitCharacters.Should().OnlyContain(s => string.Equals(s, " ")); c.Detail.Should().Be("Variable keyword"); }); }
private IEnumerable <string> GetAllDiagnostics(string text, string bicepFilePath) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(text)); var lineStarts = TextCoordinateConverter.GetLineStarts(text); return(compilation.GetSemanticModel() .GetAllDiagnostics() .Select(d => { var(line, character) = TextCoordinateConverter.GetPosition(lineStarts, d.Span.Position); return $"{bicepFilePath}({line + 1},{character + 1}) : {d.Level} {d.Code}: {d.Message}"; })); }
public void DeclaringSymbolWithFunctionNameShouldHideTheFunctionCompletion() { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(@" param concat string var resourceGroup = true resource base64 'Microsoft.Foo/foos@2020-09-01' = { name: 'foo' } output length int = 42 ")); compilation.GetSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(); var completions = provider.GetFilteredCompletions(compilation.GetSemanticModel(), new BicepCompletionContext(BicepCompletionContextKind.None)).ToList(); AssertExpectedFunctions(completions, new[] { "concat", "resourceGroup", "base64" }); // outputs can't be referenced so they should not show up in completions completions.Where(c => c.Kind == SymbolKind.Output.ToCompletionItemKind()).Should().BeEmpty(); const string expectedVariable = "resourceGroup"; var variableCompletion = completions.Single(c => c.Kind == SymbolKind.Variable.ToCompletionItemKind()); variableCompletion.Label.Should().Be(expectedVariable); variableCompletion.Kind.Should().Be(CompletionItemKind.Variable); variableCompletion.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); variableCompletion.InsertText.Should().Be(expectedVariable); variableCompletion.CommitCharacters.Should().BeNull(); variableCompletion.Detail.Should().Be(expectedVariable); const string expectedResource = "base64"; var resourceCompletion = completions.Single(c => c.Kind == SymbolKind.Resource.ToCompletionItemKind()); resourceCompletion.Label.Should().Be(expectedResource); resourceCompletion.Kind.Should().Be(CompletionItemKind.Interface); resourceCompletion.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); resourceCompletion.InsertText.Should().Be(expectedResource); resourceCompletion.CommitCharacters.Should().BeNull(); resourceCompletion.Detail.Should().Be(expectedResource); const string expectedParam = "concat"; var paramCompletion = completions.Single(c => c.Kind == SymbolKind.Parameter.ToCompletionItemKind()); paramCompletion.Label.Should().Be(expectedParam); paramCompletion.Kind.Should().Be(CompletionItemKind.Field); paramCompletion.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); paramCompletion.InsertText.Should().Be(expectedParam); paramCompletion.CommitCharacters.Should().BeNull(); paramCompletion.Detail.Should().Be(expectedParam); }
public void NameBindingsShouldBeConsistent(DataSet dataSet) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var symbolReferences = GetSymbolReferences(compilation.ProgramSyntax); // just a sanity check symbolReferences.Should().AllBeAssignableTo <ISymbolReference>(); var model = compilation.GetSemanticModel(); foreach (SyntaxBase symbolReference in symbolReferences) { var symbol = model.GetSymbolInfo(symbolReference); symbol.Should().NotBeNull(); if (dataSet.IsValid) { // valid cases should not return error symbols for any symbol reference node symbol.Should().NotBeOfType <ErrorSymbol>(); symbol.Should().Match(s => s is ParameterSymbol || s is VariableSymbol || s is ResourceSymbol || s is OutputSymbol || s is FunctionSymbol); } else { // invalid files may return errors symbol.Should().Match(s => s is ErrorSymbol || s is ParameterSymbol || s is VariableSymbol || s is ResourceSymbol || s is OutputSymbol || s is FunctionSymbol); } var foundRefs = model.FindReferences(symbol !); // the returned references should contain the original ref that we used to find the symbol foundRefs.Should().Contain(symbolReference); // each ref should map to the same exact symbol foreach (SyntaxBase foundRef in foundRefs) { model.GetSymbolInfo(foundRef).Should().BeSameAs(symbol); } } }
public void NonDeclarationContextInEmptyFileShouldReturnFunctionCompletions() { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(string.Empty)); compilation.GetSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(); var completions = provider.GetFilteredCompletions(compilation.GetSemanticModel(), new BicepCompletionContext(BicepCompletionContextKind.None)).ToList(); completions.Where(c => c.Kind == SymbolKind.Variable.ToCompletionItemKind()).Should().BeEmpty(); completions.Where(c => c.Kind == SymbolKind.Output.ToCompletionItemKind()).Should().BeEmpty(); completions.Where(c => c.Kind == SymbolKind.Resource.ToCompletionItemKind()).Should().BeEmpty(); completions.Where(c => c.Kind == SymbolKind.Parameter.ToCompletionItemKind()).Should().BeEmpty(); AssertExpectedFunctions(completions); }
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.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), 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 = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); edit.DocumentChanges.Should().BeNullOrEmpty(); edit.Changes.Should().BeNull(); } }
public void FindReferencesResultsShouldIncludeAllSymbolReferenceSyntaxNodes(DataSet dataSet) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var symbolReferences = GetSymbolReferences(compilation.ProgramSyntax); var symbols = symbolReferences .Select(symRef => compilation.GetSemanticModel().GetSymbolInfo(symRef)) .Distinct(); symbols.Should().NotContainNulls(); var foundReferences = symbols .SelectMany(s => compilation.GetSemanticModel().FindReferences(s !)) .Where(refSyntax => !(refSyntax is IDeclarationSyntax)); foundReferences.Should().BeEquivalentTo(symbolReferences); }
public void ShouldConvertExpressionsCorrectly(string text, string expected) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(string.Empty)); var parsed = ParserHelper.ParseExpression(text); var converter = new ExpressionConverter(new EmitterContext(compilation.GetSemanticModel())); var converted = converter.ConvertExpression(parsed); var serializer = new ExpressionSerializer(new ExpressionSerializerSettings { IncludeOuterSquareBrackets = true, SingleStringHandling = ExpressionSerializerSingleStringHandling.SerializeAsString }); var actual = serializer.SerializeExpression(converted); actual.Should().Be(expected); }
public async Task RenamingIdentifierAccessOrDeclarationShouldRenameDeclarationAndAllReferences(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); using var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), 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); const string expectedNewText = "NewIdentifier"; foreach (var(syntax, symbol) in validVariableAccessPairs) { var edit = await client.RequestRename(new RenameParams { NewName = expectedNewText, TextDocument = new TextDocumentIdentifier(uri), Position = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); 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); } }
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.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var symbolTable = compilation.ReconstructSymbolTable(); var lineStarts = TextCoordinateConverter.GetLineStarts(dataSet.Bicep); var unboundNodes = SyntaxAggregator.Aggregate( source: compilation.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 response = await client.RequestDefinition(new DefinitionParams { TextDocument = new TextDocumentIdentifier(uri), Position = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); // go to definition on a syntax node that isn't bound to a symbol should produce an empty response response.Should().BeEmpty(); } }
public async Task FindReferencesOnNonSymbolsShouldProduceEmptyResult(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.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var lineStarts = TextCoordinateConverter.GetLineStarts(dataSet.Bicep); 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 locations = await client.RequestReferences(new ReferenceParams { TextDocument = new TextDocumentIdentifier(uri), Context = new ReferenceContext { IncludeDeclaration = false }, Position = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); locations.Should().BeEmpty(); } }
public void ProgramsShouldProduceExpectedDiagnostics(DataSet dataSet) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var model = compilation.GetSemanticModel(); string getLoggingString(Diagnostic diagnostic) { var spanText = OutputHelper.GetSpanText(dataSet.Bicep, diagnostic); return($"[{diagnostic.Code} ({diagnostic.Level})] {diagnostic.Message} |{spanText}|"); } var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(dataSet, model.GetAllDiagnostics(), getLoggingString); var resultsFile = FileHelper.SaveResultFile(this.TestContext !, Path.Combine(dataSet.Name, DataSet.TestFileMainDiagnostics), sourceTextWithDiags); sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( dataSet.Diagnostics, expectedLocation: OutputHelper.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainDiagnostics), actualLocation: resultsFile); }
public async Task HoveringOverSymbolReferencesAndDeclarationsShouldProduceHovers(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); // construct a parallel compilation var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var symbolTable = compilation.ReconstructSymbolTable(); var lineStarts = TextCoordinateConverter.GetLineStarts(dataSet.Bicep); var symbolReferences = SyntaxAggregator.Aggregate( compilation.ProgramSyntax, new List <SyntaxBase>(), (accumulated, node) => { if (node is ISymbolReference || node is IDeclarationSyntax) { accumulated.Add(node); } return(accumulated); }, accumulated => accumulated); foreach (SyntaxBase symbolReference in symbolReferences) { var hover = await client.RequestHover(new HoverParams { TextDocument = new TextDocumentIdentifier(uri), Position = PositionHelper.GetPosition(lineStarts, symbolReference.Span.Position) }); if (symbolTable.TryGetValue(symbolReference, out var symbol) == false) { // symbol ref not bound to a symbol ValidateEmptyHover(hover); continue; } switch (symbol !.Kind) {
public async Task FindReferencesWithoutDeclarationsShouldProduceCorrectResults(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); using var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), 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 locations = await client.RequestReferences(new ReferenceParams { TextDocument = new TextDocumentIdentifier(uri), Context = new ReferenceContext { IncludeDeclaration = false }, Position = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); // 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 IDeclarationSyntax)) .Select(node => PositionHelper.GetNameRange(lineStarts, node)); // ranges should match what we got from our own symbol table locations.Select(l => l.Range).Should().BeEquivalentTo(expectedRanges); } }
public async Task GoToDefinitionRequestOnValidSymbolReferenceShouldReturnLocationOfDeclaredSymbol(DataSet dataSet) { var uri = DocumentUri.From($"/{dataSet.Name}"); using var client = await IntegrationTestHelper.StartServerWithText(dataSet.Bicep, uri); var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(dataSet.Bicep)); var symbolTable = compilation.ReconstructSymbolTable(); var lineStarts = TextCoordinateConverter.GetLineStarts(dataSet.Bicep); // filter out symbols that don't have locations var declaredSymbolBindings = symbolTable .Where(pair => pair.Value is DeclaredSymbol) .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 = PositionHelper.GetPosition(lineStarts, syntax.Span.Position) }); 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)); // origin selection range should be the span of the syntax node that references the symbol link.OriginSelectionRange.Should().Be(syntax.ToRange(lineStarts)); } }
public void ExampleIsValid(ExampleData example) { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(example.BicepContents)); var emitter = new TemplateEmitter(compilation.GetSemanticModel()); using var stream = new MemoryStream(); var result = emitter.Emit(stream); result.Diagnostics.Should().BeEmpty(); result.Status.Should().Be(EmitStatus.Succeeded); stream.Position = 0; var generated = new StreamReader(stream).ReadToEnd(); var actual = JToken.Parse(generated); FileHelper.SaveResultFile(this.TestContext !, $"{example.BicepFileName}_Compiled_Actual.json", actual.ToString(Formatting.Indented)); var expected = JToken.Parse(example.JsonContents !); FileHelper.SaveResultFile(this.TestContext !, $"{example.BicepFileName}_Compiled_Expected.json", expected.ToString(Formatting.Indented)); JsonAssert.AreEqual(expected, actual, this.TestContext !, $"{example.BicepFileName}_Compiled_Delta.json"); }
public void NormalUpsertAfterFatalException_ShouldReplaceDiagnostics() { PublishDiagnosticsParams?receivedParams = null; var document = CreateMockDocument(p => receivedParams = p); var server = CreateMockServer(document); var provider = Repository.Create <ICompilationProvider>(); const string expectedMessage = "Internal bicep exception."; // start by failing bool failUpsert = true; provider .Setup(m => m.Create(It.IsAny <string>())) .Returns <string>(text => failUpsert ? throw new InvalidOperationException(expectedMessage) : new BicepCompilationProvider(TestResourceTypeProvider.CreateRegistrar()).Create(text)); var manager = new BicepCompilationManager(server.Object, provider.Object); const long version = 74; var uri = DocumentUri.File(this.TestContext !.TestName); // upsert should fail because of the mock fatal exception manager.UpsertCompilation(uri, version, "fake").Should().BeNull(); // diagnostics should have been published once document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Once); receivedParams.Should().NotBeNull(); receivedParams !.Uri.Should().Be(uri); receivedParams.Version.Should().Be(version); receivedParams.Diagnostics.Should().HaveCount(1); var fatalDiagnostic = receivedParams.Diagnostics.Single(); fatalDiagnostic.Message.Should().Be(expectedMessage); fatalDiagnostic.Severity.Should().Be(DiagnosticSeverity.Error); // reset counts document.Invocations.Clear(); // allow success failUpsert = false; // upsert should succeed because we allowed it var upserted = manager.UpsertCompilation(uri, version, "fake\nfake\nfake\n"); upserted.Should().NotBeNull(); // new diagnostics should have been published once document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Once); receivedParams.Should().NotBeNull(); receivedParams !.Uri.Should().Be(uri); receivedParams.Version.Should().Be(version); receivedParams.Diagnostics.Should().HaveCount(3); // none of the messages should be our fatal message receivedParams.Diagnostics .Select(diag => diag.Message) .All(message => string.Equals(message, expectedMessage) == false) .Should().BeTrue(); }
public void DeclarationSnippetsShouldBeValid() { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText(string.Empty)); compilation.GetSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(); var completions = provider.GetFilteredCompletions(compilation.GetSemanticModel(), new BicepCompletionContext(BicepCompletionContextKind.Declaration)); var snippetCompletions = completions .Where(c => c.Kind == CompletionItemKind.Snippet) .OrderBy(c => c.Label) .ToList(); snippetCompletions.Should().OnlyContain(c => c.Kind == CompletionItemKind.Snippet && c.InsertTextFormat == InsertTextFormat.Snippet); snippetCompletions.Should().OnlyContain(c => c.InsertTextFormat == InsertTextFormat.Snippet); snippetCompletions.Should().OnlyContain(c => LanguageConstants.DeclarationKeywords.Contains(c.Label)); snippetCompletions.Should().OnlyContain(c => c.Documentation.HasMarkupContent && c.Documentation.MarkupContent.Kind == MarkupKind.Markdown); var snippetsByDetail = snippetCompletions.ToDictionary(c => c.Detail); var replacementsByDetail = new Dictionary <string, IList <string> > { ["Parameter declaration"] = new[] { string.Empty, "myParam", "string" }, ["Parameter declaration with default value"] = new[] { string.Empty, "myParam", "string", "'myDefault'" }, ["Parameter declaration with default and allowed values"] = new[] { string.Empty, "myParam", "string", "'myDefault'", "'val1'\n'val2'" }, ["Parameter declaration with options"] = new[] { string.Empty, "myParam", "string", "default: 'myDefault'\nsecure: true" }, ["Secure string parameter"] = new[] { string.Empty, "myParam" }, ["Variable declaration"] = new[] { "'stringVal'", "myVariable" }, ["Resource with defaults"] = new[] { "prop1: 'val1'", "myResource", "myProvider", "myType", "2020-01-01", "'parent'", "'West US'" }, ["Child Resource with defaults"] = new[] { "prop1: 'val1'", "myResource", "myProvider", "myType", "myChildType", "2020-01-01", "'parent/child'" }, ["Resource without defaults"] = new[] { "properties: {\nprop1: 'val1'\n}", "myResource", "myProvider", "myType", "2020-01-01", "'parent'" }, ["Child Resource without defaults"] = new[] { "properties: {\nprop1: 'val1'\n}", "myResource", "myProvider", "myType", "myChildType", "2020-01-01", "'parent/child'" }, ["Output declaration"] = new[] { "'stringVal'", "myOutput", "string" } }; snippetsByDetail.Keys.Should().BeEquivalentTo(replacementsByDetail.Keys); foreach (var(detail, completion) in snippetsByDetail) { // validate snippet var snippet = new Snippet(completion.InsertText); // if we don't have placeholders, why is it a snippet? snippet.Placeholders.Should().NotBeEmpty(); // documentation should have the snippet without placeholders completion.Documentation.MarkupContent.Value.Should().Contain(snippet.FormatDocumentation()); // perform the sample replacement var replacements = replacementsByDetail[detail]; var replaced = snippet.Format((s, placeholder) => placeholder.Index >= 0 && placeholder.Index < replacements.Count ? replacements[placeholder.Index] : string.Empty); var parser = new Parser(replaced); var declaration = parser.Declaration(); declaration.Should().BeAssignableTo <IDeclarationSyntax>($"because the snippet for '{detail}' failed to parse after replacements:\n{replaced}"); } }
public void EndOfFileFollowingSpaceAfterParameterKeyWordShouldNotThrow() { var compilation = new Compilation(TestResourceTypeProvider.CreateRegistrar(), SyntaxFactory.CreateFromText("parameter ")); compilation.GetSemanticModel().GetParseDiagnostics(); }
public void CloseAfterUpsert_ShouldClearDiagnostics() { PublishDiagnosticsParams?receivedParams = null; var document = CreateMockDocument(p => receivedParams = p); var server = CreateMockServer(document); var manager = new BicepCompilationManager(server.Object, new BicepCompilationProvider(TestResourceTypeProvider.CreateRegistrar())); const long version = 42; var uri = DocumentUri.File(this.TestContext !.TestName); // first get should not return anything manager.GetCompilation(uri).Should().BeNull(); // upsert the compilation manager.UpsertCompilation(uri, version, "hello"); document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Once); // there should have been 1 diagnostic receivedParams.Should().NotBeNull(); receivedParams !.Uri.Should().Be(uri); receivedParams.Version.Should().Be(version); receivedParams.Diagnostics.Should().NotBeNullOrEmpty(); receivedParams.Diagnostics.Count().Should().Be(1); // reset tracked calls document.Invocations.Clear(); // get again var actual = manager.GetCompilation(uri); actual.Should().NotBeNull(); // get should not have pushed diagnostics document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Never); // 2nd get should be the same manager.GetCompilation(uri).Should().BeSameAs(actual); // get should not have pushed diagnostics document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Never); // close compilation manager.CloseCompilation(uri); // close should have cleared diagnostics document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Once); // expect zero diagnostics and 0 version receivedParams.Should().NotBeNull(); receivedParams.Uri.Should().Be(uri); receivedParams.Version.Should().Be(0); receivedParams.Diagnostics.Should().BeEmpty(); // reset call counts document.Invocations.Clear(); // get again manager.GetCompilation(uri).Should().BeNull(); // get should not have pushed diagnostics document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Never); }