public static string AddDiagsToSourceText <T>(string bicepOutput, string newlineSequence, IEnumerable <T> items, Func <T, TextSpan> getSpanFunc, Func <T, string> diagsFunc) { var lineStarts = TextCoordinateConverter.GetLineStarts(bicepOutput); var itemsByLine = items .Select(item => { var(line, character) = TextCoordinateConverter.GetPosition(lineStarts, getSpanFunc(item).Position); return(line, character, item); }) .ToLookup(t => t.line); var sourceTextLines = bicepOutput.Split(newlineSequence); var stringBuilder = new StringBuilder(); for (var i = 0; i < sourceTextLines.Length; i++) { stringBuilder.Append(sourceTextLines[i]); stringBuilder.Append(newlineSequence); foreach (var(line, character, item) in itemsByLine[i]) { var escapedDiagsText = EscapeWhitespace(diagsFunc(item)); stringBuilder.Append($"//@[{character}:{character + getSpanFunc(item).Length}) {escapedDiagsText}"); stringBuilder.Append(newlineSequence); } } return(stringBuilder.ToString()); }
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(); } } }
private IReadOnlyList <IDiagnostic> AssembleDiagnostics() { var diagnostics = GetParseDiagnostics() .Concat(GetSemanticDiagnostics()) .Concat(GetAnalyzerDiagnostics()) .OrderBy(diag => diag.Span.Position); var filteredDiagnostics = new List <IDiagnostic>(); var disabledDiagnosticsCache = SourceFile.DisabledDiagnosticsCache; foreach (IDiagnostic diagnostic in diagnostics) { (int diagnosticLine, _) = TextCoordinateConverter.GetPosition(SourceFile.LineStarts, diagnostic.Span.Position); if (diagnosticLine == 0 || !diagnostic.CanBeSuppressed()) { filteredDiagnostics.Add(diagnostic); continue; } if (disabledDiagnosticsCache.TryGetDisabledNextLineDirective(diagnosticLine - 1) is { } disableNextLineDirectiveEndPositionAndCodes&& disableNextLineDirectiveEndPositionAndCodes.diagnosticCodes.Contains(diagnostic.Code)) { continue; } filteredDiagnostics.Add(diagnostic); } return(filteredDiagnostics); }
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); }
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) {
private static CommandOrCodeAction?DisableDiagnostic(DocumentUri documentUri, DiagnosticCode diagnosticCode, BicepFile bicepFile, TextSpan span, ImmutableArray <int> lineStarts) { if (diagnosticCode.String is null) { return(null); } var disabledDiagnosticsCache = bicepFile.DisabledDiagnosticsCache; (int diagnosticLine, _) = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, span.Position); TextEdit?textEdit; int previousLine = diagnosticLine - 1; if (disabledDiagnosticsCache.TryGetDisabledNextLineDirective(previousLine) is { } disableNextLineDirectiveEndPositionAndCodes) { textEdit = new TextEdit { Range = new Range(previousLine, disableNextLineDirectiveEndPositionAndCodes.endPosition, previousLine, disableNextLineDirectiveEndPositionAndCodes.endPosition), NewText = ' ' + diagnosticCode.String }; }
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 ProgramsShouldProduceExpectedUserDeclaredSymbols(DataSet dataSet) { var compilation = dataSet.CopyFilesAndCreateCompilation(TestContext, out var outputDirectory); var model = compilation.GetEntrypointSemanticModel(); var symbols = SymbolCollector .CollectSymbols(model) .OfType <DeclaredSymbol>(); var lineStarts = compilation.SyntaxTreeGrouping.EntryPoint.LineStarts; 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 = DataSet.AddDiagsToSourceText(dataSet, symbols, symb => symb.NameSyntax.Span, getLoggingString); var resultsFile = Path.Combine(outputDirectory, DataSet.TestFileMainDiagnostics); File.WriteAllText(resultsFile, sourceTextWithDiags); sourceTextWithDiags.Should().EqualWithLineByLineDiffOutput( TestContext, dataSet.Symbols, expectedLocation: DataSet.GetBaselineUpdatePath(dataSet, DataSet.TestFileMainSymbols), actualLocation: resultsFile); }
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(); } } }
public void GetPosition_FirstElementOfLineStartsIsNotZero_ThrowsArgumentException() { Action sut = () => TextCoordinateConverter.GetPosition(new List <int> { 42 }, 10); sut.Should().Throw <ArgumentException>().WithMessage("*must be 0, but got 42*"); }
public static string PrintWithAnnotations(SyntaxTree syntaxTree, IEnumerable <Annotation> annotations, int context, bool includeLineNumbers) { var output = new StringBuilder(); var programLines = GetProgramTextLines(syntaxTree); var annotationPositions = annotations.ToDictionary( x => x, x => TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, x.Span.Position)); var annotationsByLine = annotationPositions.ToLookup(x => x.Value.line, x => x.Key); var minLine = annotationPositions.Values.Aggregate(int.MaxValue, (min, curr) => Math.Min(curr.line, min)); var maxLine = annotationPositions.Values.Aggregate(0, (max, curr) => Math.Max(curr.line, max)) + 1; minLine = Math.Max(0, minLine - context); maxLine = Math.Min(syntaxTree.LineStarts.Length, maxLine + context); var digits = maxLine.ToString().Length; for (var i = minLine; i < maxLine; i++) { var gutterOffset = 0; if (includeLineNumbers) { var lineNumber = i + 1; // to match VSCode's line numbering (starting at 1) output.Append(lineNumber.ToString().PadLeft(digits, '0')); output.Append("| "); gutterOffset = digits + 2; } output.Append(programLines[i]); output.Append('\n'); var annotationsToDisplay = annotationsByLine[i].OrderBy(x => annotationPositions[x].character); foreach (var annotation in annotationsToDisplay) { var position = annotationPositions[annotation]; output.Append(new String(' ', gutterOffset + position.character)); switch (annotation.Span.Length) { case 0: output.Append("^"); break; case int x: // TODO handle annotation spanning multiple lines output.Append(new String('~', x)); break; } output.Append(" "); output.Append(annotation.Message); output.Append('\n'); } } return(output.ToString()); }
public void GetPosition_NegtiveOffset_ThrowsArgumentOutOfRangeException() { IReadOnlyList <int> lineStarts = new List <int> { 0, 24 }.AsReadOnly(); Action sut = () => TextCoordinateConverter.GetPosition(lineStarts, -9); sut.Should().Throw <ArgumentException>().WithMessage("*must not be a negative number*"); }
public void LogDiagnostic(Uri fileUri, Diagnostic diagnostic, ImmutableArray <int> lineStarts) { (int line, int character) = TextCoordinateConverter.GetPosition(lineStarts, diagnostic.Span.Position); string message = $"{fileUri.LocalPath}({line + 1},{character + 1}) : {diagnostic.Level} {diagnostic.Code}: {diagnostic.Message}"; this.logger.Log(ToLogLevel(diagnostic.Level), message); this.HasLoggedErrors |= diagnostic.Level == DiagnosticLevel.Error; }
public override void VisitSyntaxTrivia(SyntaxTrivia syntaxTrivia) { if (syntaxTrivia is DisableNextLineDiagnosticsSyntaxTrivia disableNextLineDiagnosticsSyntaxTrivia) { var codes = disableNextLineDiagnosticsSyntaxTrivia.DiagnosticCodes.Select(x => x.Text).ToImmutableArray(); (int line, _) = TextCoordinateConverter.GetPosition(lineStarts, syntaxTrivia.Span.Position); DisableNextLineDirectiveEndPositionAndCodes disableNextLineDirectiveEndPosAndCodes = new(syntaxTrivia.Span.GetEndPosition(), codes); disableNextLineDiagnosticDirectivesCacheBuilder.Add(line, disableNextLineDirectiveEndPosAndCodes); } }
public void LogDiagnostic(string filePath, Diagnostic diagnostic, ImmutableArray <int> lineStarts) { (int line, int character) = TextCoordinateConverter.GetPosition(lineStarts, diagnostic.Span.Position); string message = $"{filePath}({line + 1},{character + 1}) : {diagnostic.Level} {diagnostic.Code}: {diagnostic.Message}"; this.logger.Log(ToLogLevel(diagnostic.Level), message); // TODO: Fix this when we have diagnostic severity this.HasLoggedErrors = true; }
public void LogDiagnostic(Uri fileUri, IDiagnostic diagnostic, ImmutableArray <int> lineStarts) { (int line, int character) = TextCoordinateConverter.GetPosition(lineStarts, diagnostic.Span.Position); // build a a code description link if the Uri is assigned var codeDescription = diagnostic.Uri == null ? string.Empty : $" [{diagnostic.Uri.AbsoluteUri}]"; var message = $"{fileUri.LocalPath}({line + 1},{character + 1}) : {diagnostic.Level} {diagnostic.Code}: {diagnostic.Message}{codeDescription}"; this.logger.Log(ToLogLevel(diagnostic.Level), message); this.HasLoggedErrors |= diagnostic.Level == DiagnosticLevel.Error; }
private IEnumerable <string> GetAllDiagnostics(string text, string bicepFilePath) { var compilation = new Compilation(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}"; })); }
private static object ToMonacoDiagnostic(IDiagnostic diagnostic, IReadOnlyList <int> lineStarts) { var(startLine, startChar) = TextCoordinateConverter.GetPosition(lineStarts, diagnostic.Span.Position); var(endLine, endChar) = TextCoordinateConverter.GetPosition(lineStarts, diagnostic.GetEndPosition()); return(new { code = diagnostic.Code, message = diagnostic.Message, severity = ToMonacoSeverity(diagnostic.Level), startLineNumber = startLine + 1, startColumn = startChar + 1, endLineNumber = endLine + 1, endColumn = endChar + 1, }); }
private static async Task <List <LocationOrLocationLinks> > RequestDefinitions(ILanguageClient client, BicepFile bicepFile, IEnumerable <int> cursors) { var results = new List <LocationOrLocationLinks>(); foreach (var cursor in cursors) { var result = await client.RequestDefinition(new DefinitionParams() { TextDocument = new TextDocumentIdentifier(bicepFile.FileUri), Position = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, cursor) }); results.Add(result); } return(results); }
public static string GetDiagnosticsMessage(KeyValuePair <BicepFile, IEnumerable <IDiagnostic> > diagnosticsByFile) { StringBuilder sb = new StringBuilder(); IReadOnlyList <int> lineStarts = diagnosticsByFile.Key.LineStarts; foreach (IDiagnostic diagnostic in diagnosticsByFile.Value) { (int line, int character) = TextCoordinateConverter.GetPosition(lineStarts, diagnostic.Span.Position); // Build a code description link if the Uri is assigned var codeDescription = diagnostic.Uri == null ? string.Empty : $" [{diagnostic.Uri.AbsoluteUri}]"; sb.AppendLine($"{diagnosticsByFile.Key.FileUri.LocalPath}({line + 1},{character + 1}) : {diagnostic.Level} {diagnostic.Code}: {diagnostic.Message}{codeDescription}"); } return(sb.ToString()); }
private static IEnumerable <string> GetAllDiagnostics(string bicepFilePath) { var syntaxTreeGrouping = SyntaxTreeGroupingBuilder.Build(new FileResolver(), new Workspace(), PathHelper.FilePathToFileUrl(bicepFilePath)); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), syntaxTreeGrouping); var output = new List <string>(); foreach (var(syntaxTree, diagnostics) in compilation.GetAllDiagnosticsBySyntaxTree()) { foreach (var diagnostic in diagnostics) { var(line, character) = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, diagnostic.Span.Position); output.Add($"{syntaxTree.FileUri.LocalPath}({line + 1},{character + 1}) : {diagnostic.Level} {diagnostic.Code}: {diagnostic.Message}"); } } return(output); }
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"); }); }
public static string AddDiagsToSourceText <T>(string bicepOutput, string newlineSequence, IEnumerable <T> items, Func <T, TextSpan> getSpanFunc, Func <T, string> diagsFunc) { var lineStarts = TextCoordinateConverter.GetLineStarts(bicepOutput); var diagsByLine = items .Select(item => { var span = getSpanFunc(item); var(line, startChar) = TextCoordinateConverter.GetPosition(lineStarts, span.Position); var endChar = startChar + span.Length; var escapedText = EscapeWhitespace(diagsFunc(item)); return(line, startChar, endChar, escapedText); }) .ToLookup(t => t.line); var diags = diagsByLine.SelectMany(x => x); var startCharPadding = diags.Any() ? CountDigits(diags.Max(x => x.startChar)) : 0; var endCharPadding = diags.Any() ? CountDigits(diags.Max(x => x.endChar)) : 0; var sourceTextLines = bicepOutput.Split(newlineSequence); var stringBuilder = new StringBuilder(); for (var i = 0; i < sourceTextLines.Length; i++) { stringBuilder.Append(sourceTextLines[i]); stringBuilder.Append(newlineSequence); foreach (var diag in diagsByLine[i]) { var startCharPadded = diag.startChar.ToString().PadLeft(startCharPadding, '0'); var endCharPadded = diag.endChar.ToString().PadLeft(endCharPadding, '0'); // Pad the start & end char with zeros to ensure that the escaped text always starts at the same place // This makes it easier to compare lines visually stringBuilder.Append($"//@[{startCharPadded}:{endCharPadded}) {diag.escapedText}"); stringBuilder.Append(newlineSequence); } } return(stringBuilder.ToString()); }
public async Task String_segments_do_not_return_completions() { var fileWithCursors = @" var completeString = |'he|llo'| var interpolatedString = |'abc${|true}|de|f${|false}|gh|i'| var multilineString = |'''| hel|lo '''| "; var bicepFile = fileWithCursors.Replace("|", ""); var syntaxTree = SyntaxTree.Create(new Uri("file:///main.bicep"), bicepFile); var cursors = new List <int>(); for (var i = 0; i < fileWithCursors.Length; i++) { if (fileWithCursors[i] == '|') { cursors.Add(i - cursors.Count); } } using var client = await IntegrationTestHelper.StartServerWithTextAsync(bicepFile, syntaxTree.FileUri, resourceTypeProvider : TypeProvider); foreach (var cursor in cursors) { using var assertionScope = new AssertionScope(); assertionScope.AddReportable( "completion context", PrintHelper.PrintWithAnnotations(syntaxTree, new [] { new PrintHelper.Annotation(new TextSpan(cursor, 0), "cursor position"), }, 1, true)); var completions = await client.RequestCompletion(new CompletionParams { TextDocument = new TextDocumentIdentifier(syntaxTree.FileUri), Position = TextCoordinateConverter.GetPosition(syntaxTree.LineStarts, cursor), }); completions.Should().BeEmpty(); } }
protected static IEnumerable <string> GetAllDiagnostics(string bicepFilePath) { var dispatcher = new ModuleDispatcher(new DefaultModuleRegistryProvider(BicepTestConstants.FileResolver)); var sourceFileGrouping = SourceFileGroupingBuilder.Build(BicepTestConstants.FileResolver, dispatcher, new Workspace(), PathHelper.FilePathToFileUrl(bicepFilePath)); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), sourceFileGrouping); var output = new List <string>(); foreach (var(bicepFile, diagnostics) in compilation.GetAllDiagnosticsByBicepFile()) { foreach (var diagnostic in diagnostics) { var(line, character) = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, diagnostic.Span.Position); var codeDescription = diagnostic.Uri == null ? string.Empty : $" [{diagnostic.Uri.AbsoluteUri}]"; output.Add($"{bicepFile.FileUri.LocalPath}({line + 1},{character + 1}) : {diagnostic.Level} {diagnostic.Code}: {diagnostic.Message}{codeDescription}"); } } return(output); }
public void GetPosition_EmptyLineStarts_ThrowsArgumentException() { Action sut = () => TextCoordinateConverter.GetPosition(new List <int>().AsReadOnly(), 10); sut.Should().Throw <ArgumentException>().WithMessage("*must not be empty*"); }
public async Task VerifyResourceBodyCompletionWithDiscriminatedObjectTypeContainsRequiredPropertiesSnippet() { string text = @"resource deploymentScripts 'Microsoft.Resources/deploymentScripts@2020-10-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.InsertTextFormat.Should().Be(InsertTextFormat.Snippet); c.Label.Should().Be("required-properties-AzureCLI"); c.Detail.Should().Be("Required properties"); c.TextEdit?.NewText?.Should().BeEquivalentToIgnoringNewlines(@"{ name: $1 location: $2 kind: 'AzureCLI' properties: { azCliVersion: $3 retentionInterval: $4 } $0 }"); }, c => { c.Label.Should().Be("required-properties-AzurePowerShell"); c.Detail.Should().Be("Required properties"); c.TextEdit?.NewText?.Should().BeEquivalentToIgnoringNewlines(@"{ name: $1 location: $2 kind: 'AzurePowerShell' properties: { azPowerShellVersion: $3 retentionInterval: $4 } $0 }"); }, 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"); }); }