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 = new Compilation(TestResourceTypeProvider.Create(), 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.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 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 RequestingHighlightsForWrongNodeShouldProduceNoHighlights(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 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 highlights = await client.RequestDocumentHighlight(new DocumentHighlightParams { TextDocument = new TextDocumentIdentifier(uri), Position = IntegrationTestHelper.GetPosition(lineStarts, syntax) }); highlights.Should().BeEmpty(); } }
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.Create(), 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 Module_self_cycle_is_detected_correctly() { var files = new Dictionary <string, string> { ["/main.bicep"] = @" param inputa string param inputb string module mainRecursive 'main.bicep' = { name: 'mainRecursive' params: { inputa: inputa inputb: inputb } } ", }; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxFactory.CreateForFiles(files, "/main.bicep")); var(success, diagnosticsByFile) = GetSuccessAndDiagnosticsByFile(compilation); diagnosticsByFile["/main.bicep"].Should().HaveDiagnostics(new[] {
public async Task RequestingCodeActionWithNonFixableDiagnosticsShouldProduceEmptyQuickFixes(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 nonFixables = compilation.GetSemanticModel().GetAllDiagnostics().Where(diagnostic => !(diagnostic is IFixable)); foreach (var nonFixable in nonFixables) { CommandOrCodeActionContainer?quickFixes = await client.RequestCodeAction(new CodeActionParams { TextDocument = new TextDocumentIdentifier(uri), Range = nonFixable.Span.ToRange(lineStarts) }); // Assert. quickFixes.Should().NotBeNull(); quickFixes.Should().BeEmpty(); } }
public void NestedResources_resource_can_contain_property_called_resource() { var program = @" resource parent 'My.RP/parentType@2020-01-01' = { name: 'parent' properties: { size: 'large' } resource: 'yes please' resource child 'childType' = { name: 'child' properties: { style: 'very cool' } } } "; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); var model = compilation.GetEntrypointSemanticModel(); // The property "resource" is not allowed ... model.GetAllDiagnostics().Should().HaveCount(1); model.GetAllDiagnostics().Single().Should().HaveCodeAndSeverity("BCP038", DiagnosticLevel.Error); var expected = new [] { new { name = "child", type = "My.RP/parentType/childType@2020-01-01", }, new { name = "parent", type = "My.RP/parentType@2020-01-01", }, }; model.Root.GetAllResourceDeclarations() .Select(s => new { name = s.Name, type = (s.Type as ResourceType)?.TypeReference.FormatName(), }) .OrderBy(n => n.name) .Should().BeEquivalentTo(expected); }
public void DeclarationSnippetsShouldBeValid() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(string.Empty); var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().BeEmpty(); ResourceSnippetsProvider resourceSnippetsProvider = new ResourceSnippetsProvider(); var provider = new BicepCompletionProvider(new FileResolver(), resourceSnippetsProvider); var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(compilation, 0)); var snippetCompletions = completions .Where(c => c.Kind == CompletionItemKind.Snippet) .OrderBy(c => c.Label) .ToList(); IEnumerable <ResourceSnippet> resourceSnippets = resourceSnippetsProvider.GetResourceSnippets(); 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) || resourceSnippets.Any(rs => rs.Name == c.Label)); snippetCompletions.Should().OnlyContain(c => c.Documentation !.HasMarkupContent && c.Documentation.MarkupContent !.Kind == MarkupKind.Markdown); var snippetsByDetail = snippetCompletions.Where(c => c.Detail != null).ToImmutableDictionaryExcludingNull(c => c.Detail, StringComparer.Ordinal); var replacementsByDetail = new Dictionary <string, IList <string> > { ["Module declaration"] = new[] { string.Empty, "myModule", "./empty.bicep" }, ["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" }, ["Kubernetes Service Cluster"] = new[] { "aksCluster", "1.5", "prefix", "2", "Standard_All", "userName", "keyData", "appId", "test" }, ["Application Security Group"] = new[] { "myApplicationSecurityGroup" }, ["Automation Account"] = new[] { "myAutomationAccount", "Basic" }, ["Availability Set"] = new[] { "availabilitySet", "availabilitySet" }, ["Container Group"] = new[] { "myContainerGroup", "container", "image", "80", "1", "4", "Linux", "TCP", "80" }, ["Container Registry"] = new[] { "myContainerRegistry", "Basic", "true" }, ["Cosmos DB Database Account"] = new[] { "myCosmosDBAccount", "MongoDB", "session", "1", "5", "location", "0", "filter", "false", "EnableTable" }, ["Data Lake Store Account"] = new[] { "myDataLakeStore", "Consumption", "Enabled" }, ["DNS Zone"] = new[] { "dnsZone" }, ["Public IP Address"] = new[] { "192.168.1.10", "192.168.1.10", "dnsName" }, ["Public IP Prefix"] = new[] { "publicIpPrefix", "28" } }; snippetsByDetail.Keys.Should().BeEquivalentTo(replacementsByDetail.Keys); foreach (var(detail, completion) in snippetsByDetail) { // validate snippet var snippet = new Snippet(completion.TextEdit !.NewText); // 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 !];
public void UpsertCompilation_ShouldUpsertSuccessfully() { PublishDiagnosticsParams?receivedParams = null; var document = CreateMockDocument(p => receivedParams = p); var server = CreateMockServer(document); var manager = new BicepCompilationManager(server.Object, new BicepCompilationProvider(TestResourceTypeProvider.Create())); 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 CompilationContext?upserted = 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(); // should be the same object actual.Should().BeSameAs(upserted); // get should not have pushed diagnostics document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Never); }
private static Program CreateProgram(TextWriter outputWriter, TextWriter errorWriter) { return(new Program(TestResourceTypeProvider.Create(), outputWriter, errorWriter, BicepTestConstants.DevAssemblyFileVersion)); }
public void UpsertCompilation_ShouldUpdateDiagnostics() { PublishDiagnosticsParams?receivedParams = null; var document = CreateMockDocument(p => receivedParams = p); var server = CreateMockServer(document); var manager = new BicepCompilationManager(server.Object, new BicepCompilationProvider(TestResourceTypeProvider.Create(), CreateEmptyFileResolver()), new Workspace()); const int 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"); var firstUpserted = manager.GetCompilation(uri); // should have pushed out diagnostics 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 firstActual = manager.GetCompilation(uri); firstActual.Should().NotBeNull(); // should be same as first upserted firstActual.Should().BeSameAs(firstUpserted); // upsert second one const int newVersion = version + 1; manager.UpsertCompilation(uri, newVersion, "hello\r\nthere\r\n"); var secondUpserted = manager.GetCompilation(uri); secondUpserted.Should().NotBeNull(); secondUpserted.Should().NotBeSameAs(firstUpserted); // should have pushed out new diagnostics document.Verify(m => m.SendNotification(It.IsAny <PublishDiagnosticsParams>()), Times.Once); // reset invocations document.Invocations.Clear(); // there should have been 2 diagnostics receivedParams.Should().NotBeNull(); receivedParams !.Uri.Should().Be(uri); receivedParams.Version.Should().Be(newVersion); receivedParams.Diagnostics.Should().NotBeNullOrEmpty(); receivedParams.Diagnostics.Count().Should().Be(2); // get latest var secondActual = manager.GetCompilation(uri); secondActual.Should().BeSameAs(secondUpserted); }
private static Program CreateProgram(TextWriter outputWriter, TextWriter errorWriter) { return(new Program(TestResourceTypeProvider.Create(), outputWriter, errorWriter)); }
public void Decompiler_handles_banned_function_replacement(string expression, string type, string expectedValue) { var template = @"{ ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"", ""contentVersion"": ""1.0.0.0"", ""parameters"": {}, ""variables"": { ""a"": true, ""b"": false, ""c"": true }, ""resources"": [], ""outputs"": { ""calculated"": { ""type"": """ + type + @""", ""value"": ""[" + expression + @"]"" } } }"; var fileUri = new Uri("file:///path/to/main.json"); var fileResolver = new InMemoryFileResolver(new Dictionary <Uri, string> { [fileUri] = template, });; var(entryPointUri, filesToSave) = TemplateDecompiler.DecompileFileWithModules(TestResourceTypeProvider.Create(), fileResolver, fileUri); filesToSave[entryPointUri].Should().Contain($"output calculated {type} = ({expectedValue})"); }
public void EndOfFileFollowingSpaceAfterParameterKeyWordShouldNotThrow() { var compilation = new Compilation(TestResourceTypeProvider.Create(), 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.Create(), CreateEmptyFileResolver()), new Workspace()); const int 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); }
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."; const int version = 74; var uri = DocumentUri.File(this.TestContext.TestName); // start by failing bool failUpsert = true; provider .Setup(m => m.Create(It.IsAny <IReadOnlyWorkspace>(), It.IsAny <DocumentUri>())) .Returns <IReadOnlyWorkspace, DocumentUri>((workspace, documentUri) => failUpsert ? throw new InvalidOperationException(expectedMessage) : new BicepCompilationProvider(TestResourceTypeProvider.Create(), CreateEmptyFileResolver()).Create(workspace, documentUri)); var manager = new BicepCompilationManager(server.Object, provider.Object, new Workspace()); // upsert should fail because of the mock fatal exception manager.UpsertCompilation(uri, version, "fake"); manager.GetCompilation(uri).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 manager.UpsertCompilation(uri, version, "fake\nfake\nfake\n"); var upserted = manager.GetCompilation(uri); 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 CloseNonExistentCompilation_ShouldClearDiagnostics() { PublishDiagnosticsParams?receivedParams = null; var document = CreateMockDocument(p => receivedParams = p); var server = CreateMockServer(document); var manager = new BicepCompilationManager(server.Object, new BicepCompilationProvider(TestResourceTypeProvider.Create(), CreateEmptyFileResolver()), new Workspace()); var uri = DocumentUri.File(this.TestContext.TestName); 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(); }
public void GetNonExistentCompilation_ShouldNotThrow() { var server = Repository.Create <ILanguageServerFacade>(); var manager = new BicepCompilationManager(server.Object, new BicepCompilationProvider(TestResourceTypeProvider.Create(), CreateEmptyFileResolver()), new Workspace()); var uri = DocumentUri.File(this.TestContext.TestName); manager.GetCompilation(uri).Should().BeNull(); }
public void NestedResources_valid_resource_references() { var program = @" resource parent 'My.RP/parentType@2020-01-01' = { name: 'parent' properties: { size: 'large' } resource child 'childType' = { name: 'child' properties: { style: 'very cool' } resource grandchild 'grandchildType' = { name: 'grandchild' properties: { temperature: 'ice-cold' } } } resource sibling 'childType@2020-01-02' = { name: 'sibling' properties: { style: parent::child.properties.style size: parent.properties.size temperatureC: child::grandchild.properties.temperature temperatureF: parent::child::grandchild.properties.temperature } } } output fromChild string = parent::child.properties.style output fromGrandchild string = parent::child::grandchild.properties.style "; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); var model = compilation.GetEntrypointSemanticModel(); model.GetAllDiagnostics().Should().BeEmpty(); var parent = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "parent"); var references = model.FindReferences(parent); references.Should().HaveCount(6); var child = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "child"); references = model.FindReferences(child); references.Should().HaveCount(6); var grandchild = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "grandchild"); references = model.FindReferences(grandchild); references.Should().HaveCount(4); var sibling = model.Root.GetAllResourceDeclarations().Single(r => r.Name == "sibling"); references = model.FindReferences(sibling); references.Should().HaveCount(1); var emitter = new TemplateEmitter(compilation.GetEntrypointSemanticModel(), BicepTestConstants.DevAssemblyFileVersion); using var outputStream = new MemoryStream(); emitter.Emit(outputStream); outputStream.Seek(0L, SeekOrigin.Begin); var text = Encoding.UTF8.GetString(outputStream.GetBuffer()); }
public void Decompiler_handles_strings_with_newlines(string newline, string escapedNewline) { var template = @"{ ""$schema"": ""https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"", ""contentVersion"": ""1.0.0.0"", ""parameters"": {}, ""variables"": { ""multilineString"": ""multi line string"" }, ""resources"": [], ""outputs"": {} }"; // replace newlines with the style passed in template = string.Join(newline, Regex.Split(template, "\r?\n")); var fileUri = new Uri("file:///path/to/main.json"); var fileResolver = new InMemoryFileResolver(new Dictionary <Uri, string> { [fileUri] = template, });; var(entryPointUri, filesToSave) = TemplateDecompiler.DecompileFileWithModules(TestResourceTypeProvider.Create(), fileResolver, fileUri); // this behavior is actaully controlled by newtonsoft's deserializer, but we should assert it anyway to avoid regressions. filesToSave[entryPointUri].Should().Contain($"var multilineString = 'multi{escapedNewline} line{escapedNewline} string'"); }
public void DeclarationSnippetsShouldBeValid() { var compilation = new Compilation(TestResourceTypeProvider.Create(), 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 DeclarationContextShouldReturnKeywordCompletions() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(string.Empty); var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider()); var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(compilation, 0)); var keywordCompletions = completions .Where(c => c.Kind == CompletionItemKind.Keyword) .OrderBy(c => c.Label) .ToList(); keywordCompletions.Should().SatisfyRespectively( c => { c.Label.Should().Be("module"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().BeNull(); c.Detail.Should().Be("Module keyword"); c.TextEdit !.NewText.Should().Be("module"); }, c => { c.Label.Should().Be("output"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().BeNull(); c.Detail.Should().Be("Output keyword"); c.TextEdit !.NewText.Should().Be("output"); }, c => { c.Label.Should().Be("param"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().BeNull(); c.Detail.Should().Be("Parameter keyword"); c.TextEdit !.NewText.Should().Be("param"); }, c => { c.Label.Should().Be("resource"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().BeNull(); c.Detail.Should().Be("Resource keyword"); c.TextEdit !.NewText.Should().Be("resource"); }, c => { c.Label.Should().Be("targetScope"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().BeNull(); c.Detail.Should().Be("Target Scope keyword"); c.TextEdit !.NewText.Should().Be("targetScope"); }, c => { c.Label.Should().Be("var"); c.Kind.Should().Be(CompletionItemKind.Keyword); c.InsertTextFormat.Should().Be(InsertTextFormat.PlainText); c.InsertText.Should().BeNull(); c.Detail.Should().Be("Variable keyword"); c.TextEdit !.NewText.Should().Be("var"); }); }