public void NestedResources_invalid_resource_references() { var program = @" var notResource = 'hi' 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' } } } } output fromVariable string = notResource::child.properties.style output fromChildInvalid string = parent::child2.properties.style output fromGrandchildInvalid string = parent::child::cousin.properties.temperature "; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); var model = compilation.GetEntrypointSemanticModel(); model.GetAllDiagnostics().Should().HaveDiagnostics(new [] {
public void Module_self_cycle_is_detected_correctly() { var mainUri = new Uri("file:///main.bicep"); var files = new Dictionary <Uri, string> { [mainUri] = @" param inputa string param inputb string module mainRecursive 'main.bicep' = { name: 'mainRecursive' params: { inputa: inputa inputb: inputb } } ", }; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateForFiles(files, mainUri)); var(success, diagnosticsByFile) = GetSuccessAndDiagnosticsByFile(compilation); diagnosticsByFile[mainUri].Should().HaveDiagnostics(new[] {
public void CompletionsForOneLinerParameterDefaultValueShouldIncludeFunctionsValidInDefaultValues() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(@"param p string = "); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); var offset = ((ParameterDefaultValueSyntax)grouping.EntryPoint.ProgramSyntax.Declarations.OfType <ParameterDeclarationSyntax>().Single().Modifier !).DefaultValue.Span.Position; var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); var completions = provider.GetFilteredCompletions( compilation, BicepCompletionContext.Create(compilation, offset)).ToList(); AssertExpectedFunctions(completions, expectParamDefaultFunctions: true); // outputs can't be referenced so they should not show up in completions completions.Where(c => c.Kind == SymbolKind.Output.ToCompletionItemKind()).Should().BeEmpty(); completions.Where(c => c.Kind == SymbolKind.Variable.ToCompletionItemKind()).Should().BeEmpty(); completions.Where(c => c.Kind == SymbolKind.Resource.ToCompletionItemKind()).Should().BeEmpty(); completions.Where(c => c.Kind == SymbolKind.Module.ToCompletionItemKind()).Should().BeEmpty(); // should not see parameter completions because we set the enclosing declaration which will exclude the corresponding symbol // this avoids cycle suggestions completions.Where(c => c.Kind == SymbolKind.Parameter.ToCompletionItemKind()).Should().BeEmpty(); }
public void LockedModeShouldBlockAccess() { const string expectedMessage = "Properties of the symbol context should not be accessed until name binding is completed."; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText("")); var bindings = new Dictionary <SyntaxBase, Symbol>(); var cyclesBySymbol = new Dictionary <DeclaredSymbol, ImmutableArray <DeclaredSymbol> >(); var context = new SymbolContext(compilation, compilation.GetEntrypointSemanticModel()); Action byName = () => { var tm = context.TypeManager; }; byName.Should().Throw <InvalidOperationException>().WithMessage(expectedMessage); Action byNode = () => { var b = context.Compilation; }; byNode.Should().Throw <InvalidOperationException>().WithMessage(expectedMessage); context.Unlock(); context.TypeManager.Should().NotBeNull(); context.Compilation.Should().NotBeNull(); }
public void PrintHelper_should_add_annotations() { var syntaxTree = SyntaxTreeGroupingFactory.CreateFromText(@" resource domainServices 'Microsoft.MadeUpRp/madeUpType@2017-06-01' = { name: 'hello' location: location properties: { someMadeUpProp: 'boo' } } ").EntryPoint; var output = PrintHelper.PrintWithAnnotations(syntaxTree, new [] { new PrintHelper.Annotation(new TextSpan(26, 18), "what is this!?"), // 0 length span should produce a '^' rather than a '~' new PrintHelper.Annotation(new TextSpan(80, 0), "oh, hi!"), new PrintHelper.Annotation(new TextSpan(129, 14), "i can't believe you've done this"), }, 1, true); output.Should().Be( @"1| 2| resource domainServices 'Microsoft.MadeUpRp/madeUpType@2017-06-01' = { ~~~~~~~~~~~~~~~~~~ what is this!? 3| name: 'hello' ^ oh, hi! 4| location: location 5| properties: { 6| someMadeUpProp: 'boo' ~~~~~~~~~~~~~~ i can't believe you've done this 7| } "); }
private static SemanticModel GetSemanticModelForTest(string programText, IEnumerable <ResourceType> definedTypes) { var typeProvider = TestTypeHelper.CreateProviderWithTypes(definedTypes); var compilation = new Compilation(typeProvider, SyntaxTreeGroupingFactory.CreateFromText(programText, BicepTestConstants.FileResolver)); return(compilation.GetEntrypointSemanticModel()); }
public void EmptyProgram_SyntaxTreeGrouping_should_be_persisted() { var program = SyntaxTreeGroupingFactory.CreateFromText(DataSets.Empty.Bicep); var compilation = new Compilation(TestResourceTypeProvider.Create(), program); compilation.SyntaxTreeGrouping.Should().BeSameAs(program); compilation.GetEntrypointSemanticModel().Should().NotBeNull(); }
public void VisitorShouldProduceNoChainForNonInlinedVariables(string variableName) { var compilation = new Compilation(new AzResourceTypeProvider(), SyntaxTreeGroupingFactory.CreateFromText(Text)); VariableDeclarationSyntax variable = GetVariableByName(compilation, variableName); InlineDependencyVisitor.ShouldInlineVariable(compilation.GetEntrypointSemanticModel(), variable, out var chain).Should().BeFalse(); chain.Should().BeEmpty(); }
private static SemanticModel GetSemanticModelForTest(string programText, IEnumerable <ResourceType> definedTypes) { var typeProvider = ResourceTypeProviderHelper.CreateMockTypeProvider(definedTypes); var compilation = new Compilation(typeProvider, SyntaxTreeGroupingFactory.CreateFromText(programText)); return(compilation.GetEntrypointSemanticModel()); }
public void ZeroMatchingNodes_Create_ShouldThrow() { const string text = "var foo = 42"; var compilation = new Compilation(new AzResourceTypeProvider(), SyntaxTreeGroupingFactory.CreateFromText(text)); Action fail = () => BicepCompletionContext.Create(compilation, text.Length + 2); fail.Should().Throw <ArgumentException>().WithMessage("The specified offset 14 is outside the span of the specified ProgramSyntax node."); }
public void DeclarationSnippetsShouldBeValid() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(string.Empty); var compilation = new Compilation(TestResourceTypeProvider.Create(), grouping); compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(new FileResolver()); var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(grouping.EntryPoint, 0)); 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.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" } }; 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 async Task ValidateSnippetCompletionAfterPlaceholderReplacements(CompletionData completionData) { string pathPrefix = $"Completions/SnippetTemplates/{completionData.Prefix}"; var outputDirectory = FileHelper.SaveEmbeddedResourcesWithPathPrefix(TestContext, typeof(CompletionTests).Assembly, pathPrefix); var bicepFileName = Path.Combine(outputDirectory, "main.bicep"); var bicepSourceFileName = Path.Combine("src", "Bicep.LangServer.IntegrationTests", pathPrefix, Path.GetRelativePath(outputDirectory, bicepFileName)); File.Exists(bicepFileName).Should().BeTrue($"Snippet placeholder file \"{bicepSourceFileName}\" should be checked in"); var bicepContents = await File.ReadAllTextAsync(bicepFileName); bicepContents = StringUtils.ReplaceNewlines(bicepContents, "\n"); var cursor = bicepContents.IndexOf("// Insert snippet here"); // Request the expected completion from the server, and ensure it is unique + valid var completionText = await RequestSnippetCompletion(bicepFileName, completionData, bicepContents, cursor); // Replace all the placeholders with values from the placeholder file var replacementContents = SnippetCompletionTestHelper.GetSnippetTextAfterPlaceholderReplacements(completionText, bicepContents); var bicepContentsReplaced = bicepContents.Substring(0, cursor) + replacementContents + bicepContents.Substring(cursor); using (new AssertionScope()) { var combinedFileName = Path.Combine(outputDirectory, "main.combined.bicep"); var combinedSourceFileName = Path.Combine("src", "Bicep.LangServer.IntegrationTests", pathPrefix, Path.GetRelativePath(outputDirectory, combinedFileName)); File.Exists(combinedFileName).Should().BeTrue($"Combined snippet file \"{combinedSourceFileName}\" should be checked in"); var combinedFileUri = PathHelper.FilePathToFileUrl(combinedFileName); var syntaxTreeGrouping = SyntaxTreeGroupingFactory.CreateForFiles(new Dictionary <Uri, string> { [combinedFileUri] = bicepContentsReplaced, }, combinedFileUri, BicepTestConstants.FileResolver); var compilation = new Compilation(TypeProvider, syntaxTreeGrouping); var diagnostics = compilation.GetEntrypointSemanticModel().GetAllDiagnostics(); if (diagnostics.Any()) { var sourceTextWithDiags = OutputHelper.AddDiagsToSourceText(bicepContentsReplaced, "\n", diagnostics, diag => OutputHelper.GetDiagLoggingString(bicepContentsReplaced, outputDirectory, diag)); Execute.Assertion.FailWith($"Expected \"main.combined.bicep\" file to not contain errors or warnings, but found {diagnostics.Count()}. " + $"Please fix errors/warnings mentioned in below section in \"main.combined.bicep\" file:\n " + $"{sourceTextWithDiags}"); } bicepContentsReplaced.Should().EqualWithLineByLineDiffOutput( TestContext, File.Exists(combinedFileName) ? (await File.ReadAllTextAsync(combinedFileName)) : string.Empty, expectedLocation: combinedSourceFileName, actualLocation: combinedFileName + ".actual"); } }
public void CommentShouldNotGiveAnyCompletions(string codeFragment) { var grouping = SyntaxTreeGroupingFactory.CreateFromText(codeFragment); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); var offset = codeFragment.IndexOf('|'); var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(compilation, offset)); completions.Should().BeEmpty(); }
public void VisitorShouldProduceCorrectChainForInlinedVariables(string variableName, string expectedChain) { var compilation = new Compilation(new AzResourceTypeProvider(), SyntaxTreeGroupingFactory.CreateFromText(Text)); VariableDeclarationSyntax variable = GetVariableByName(compilation, variableName); InlineDependencyVisitor.ShouldInlineVariable(compilation.GetEntrypointSemanticModel(), variable, out var chain).Should().BeTrue(); chain.Should().NotBeNull(); var actualChain = string.Join(',', (IEnumerable <string>)chain !); actualChain.Should().Be(expectedChain); }
public void Modules_can_be_compiled_successfully() { var mainUri = new Uri("file:///main.bicep"); var moduleAUri = new Uri("file:///modulea.bicep"); var moduleBUri = new Uri("file:///moduleb.bicep"); var files = new Dictionary <Uri, string> { [mainUri] = @" param inputa string param inputb string module modulea 'modulea.bicep' = { name: 'modulea' params: { inputa: inputa inputb: inputb } } module moduleb 'moduleb.bicep' = { name: 'moduleb' params: { inputa: inputa inputb: inputb } } output outputa string = modulea.outputs.outputa output outputb string = moduleb.outputs.outputb ", [moduleAUri] = @" param inputa string param inputb string output outputa string = '${inputa}-${inputb}' ", [moduleBUri] = @" param inputa string param inputb string output outputb string = '${inputa}-${inputb}' ", }; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateForFiles(files, mainUri)); var(success, diagnosticsByFile) = GetSuccessAndDiagnosticsByFile(compilation); diagnosticsByFile.Values.SelectMany(x => x).Should().BeEmpty(); success.Should().BeTrue(); GetTemplate(compilation).Should().NotBeEmpty(); }
public void DeclaringSymbolWithFunctionNameShouldHideTheFunctionCompletion() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(@" param concat string var resourceGroup = true resource base64 'Microsoft.Foo/foos@2020-09-01' = { name: 'foo' } output length int = "); var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType <OutputDeclarationSyntax>().Single().Value.Span.Position; var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); var context = BicepCompletionContext.Create(compilation, offset); var completions = provider.GetFilteredCompletions(compilation, context).ToList(); AssertExpectedFunctions(completions, expectParamDefaultFunctions: false, new[] { "sys.concat", "az.resourceGroup", "sys.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.TextEdit !.TextEdit !.NewText.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.TextEdit !.TextEdit !.NewText.Should().Be(expectedResource); resourceCompletion.CommitCharacters.Should().BeEquivalentTo(new [] { ":", }); 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.TextEdit !.TextEdit !.NewText.Should().Be(expectedParam); paramCompletion.CommitCharacters.Should().BeNull(); paramCompletion.Detail.Should().Be(expectedParam); }
public void AzResourceTypeProvider_should_warn_for_missing_resource_types() { Compilation createCompilation(string program) => new Compilation(AzResourceTypeProvider.CreateWithAzTypes(), SyntaxTreeGroupingFactory.CreateFromText(program)); // Missing top-level properties - should be an error var compilation = createCompilation(@" resource missingResource 'Mock.Rp/madeUpResourceType@2020-01-01' = { name: 'missingResource' } "); compilation.Should().HaveDiagnostics(new [] {
public void VisitorShouldCalculateInliningInBulk() { var compilation = new Compilation(new AzResourceTypeProvider(), SyntaxTreeGroupingFactory.CreateFromText(Text)); var inlineVariables = InlineDependencyVisitor.GetVariablesToInline(compilation.GetEntrypointSemanticModel()); inlineVariables.Should().Contain(new[] { GetVariableSymbolByName(compilation, "keys"), GetVariableSymbolByName(compilation, "indirection"), GetVariableSymbolByName(compilation, "runtimeLoop"), GetVariableSymbolByName(compilation, "runtimeLoop2") }); }
public void OutputTypeContextShouldReturnDeclarationTypeCompletions() { var grouping = SyntaxTreeGroupingFactory.CreateFromText("output test "); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType <OutputDeclarationSyntax>().Single().Type.Span.Position; var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(compilation, offset)); var declarationTypeCompletions = completions.Where(c => c.Kind == CompletionItemKind.Class).ToList(); AssertExpectedDeclarationTypeCompletions(declarationTypeCompletions); completions.Where(c => c.Kind == CompletionItemKind.Snippet).Should().BeEmpty(); }
public void PrintHelper_only_includes_nearby_context() { var syntaxTree = SyntaxTreeGroupingFactory.CreateFromText(@" var test = ''' here's a really long multiline string that we don't care about ''' // // give me a cursor here please! // var test = ''' here's another really long multiline string that we don't care about ''' ").EntryPoint; var output = PrintHelper.PrintWithAnnotations(syntaxTree, new [] { new PrintHelper.Annotation(new TextSpan(108, 4), "here's your cursor!"), }, 1, true); output.Should().Be( @"16| // 17| // give me a cursor here please! ~~~~ here's your cursor! 18| // "); }
public void VerifyParameterTypeCompletionWithPrecedingComment() { var grouping = SyntaxTreeGroupingFactory.CreateFromText("/*test*/param foo "); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType <ParameterDeclarationSyntax>().Single().Type.Span.Position; var completions = provider.GetFilteredCompletions(compilation, BicepCompletionContext.Create(compilation, offset)); var declarationTypeCompletions = completions.Where(c => c.Kind == CompletionItemKind.Class).ToList(); AssertExpectedDeclarationTypeCompletions(declarationTypeCompletions); completions.Where(c => c.Kind == CompletionItemKind.Snippet).Should().SatisfyRespectively( c => { c.Label.Should().Be("secureObject"); c.Kind.Should().Be(CompletionItemKind.Snippet); c.InsertTextFormat.Should().Be(InsertTextFormat.Snippet); c.TextEdit !.TextEdit !.NewText.Should().Be("object"); c.TextEdit.TextEdit.Range.Start.Line.Should().Be(0); c.TextEdit.TextEdit.Range.Start.Character.Should().Be(18); c.Detail.Should().Be("Secure object"); c.AdditionalTextEdits !.Count().Should().Be(1); c.AdditionalTextEdits !.ElementAt(0).NewText.Should().Be("@secure()\n"); c.AdditionalTextEdits !.ElementAt(0).Range.Start.Line.Should().Be(0); c.AdditionalTextEdits !.ElementAt(0).Range.Start.Character.Should().Be(8); c.AdditionalTextEdits !.ElementAt(0).Range.End.Line.Should().Be(0); c.AdditionalTextEdits !.ElementAt(0).Range.End.Character.Should().Be(8); }, c => { c.Label.Should().Be("secureString"); c.Kind.Should().Be(CompletionItemKind.Snippet); c.InsertTextFormat.Should().Be(InsertTextFormat.Snippet); c.TextEdit !.TextEdit !.NewText.Should().Be("string"); c.TextEdit.TextEdit.Range.Start.Line.Should().Be(0); c.TextEdit.TextEdit.Range.Start.Character.Should().Be(18); c.Detail.Should().Be("Secure string"); c.AdditionalTextEdits !.Count().Should().Be(1); c.AdditionalTextEdits !.ElementAt(0).NewText.Should().Be("@secure()\n"); c.AdditionalTextEdits !.ElementAt(0).Range.Start.Line.Should().Be(0); c.AdditionalTextEdits !.ElementAt(0).Range.Start.Character.Should().Be(8); c.AdditionalTextEdits !.ElementAt(0).Range.End.Line.Should().Be(0); c.AdditionalTextEdits !.ElementAt(0).Range.End.Character.Should().Be(8); }); }
public void NonDeclarationContextShouldIncludeDeclaredSymbols() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(@" param p string var v = resource r 'Microsoft.Foo/foos@2020-09-01' = { name: 'foo' } output o int = 42 "); var offset = grouping.EntryPoint.ProgramSyntax.Declarations.OfType <VariableDeclarationSyntax>().Single().Value.Span.Position; var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); var context = BicepCompletionContext.Create(compilation, offset); var completions = provider.GetFilteredCompletions(compilation, context).ToList(); AssertExpectedFunctions(completions, expectParamDefaultFunctions: false); // outputs can't be referenced so they should not show up in completions completions.Where(c => c.Kind == SymbolKind.Output.ToCompletionItemKind()).Should().BeEmpty(); // the variable won't appear in completions because we are not suggesting cycles completions.Where(c => c.Kind == SymbolKind.Variable.ToCompletionItemKind()).Should().BeEmpty(); const string expectedResource = "r"; 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.TextEdit !.TextEdit !.NewText.Should().Be(expectedResource); resourceCompletion.CommitCharacters.Should().BeEquivalentTo(new[] { ":", }); resourceCompletion.Detail.Should().Be(expectedResource); const string expectedParam = "p"; 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.TextEdit !.TextEdit !.NewText.Should().Be(expectedParam); paramCompletion.CommitCharacters.Should().BeNull(); paramCompletion.Detail.Should().Be(expectedParam); }
public void TemplateEmitter_output_should_not_include_UTF8_BOM() { var syntaxTreeGrouping = SyntaxTreeGroupingFactory.CreateFromText(""); var compiledFilePath = FileHelper.GetResultFilePath(this.TestContext, "main.json"); // emitting the template should be successful var result = this.EmitTemplate(syntaxTreeGrouping, compiledFilePath, BicepTestConstants.DevAssemblyFileVersion); result.Status.Should().Be(EmitStatus.Succeeded); result.Diagnostics.Should().BeEmpty(); var bytes = File.ReadAllBytes(compiledFilePath); // No BOM at the start of the file bytes.Take(3).Should().NotBeEquivalentTo(new [] { 0xEF, 0xBB, 0xBF }, "BOM should not be present"); bytes.First().Should().Be(0x7B, "template should always begin with a UTF-8 encoded open curly"); bytes.Last().Should().Be(0x7D, "template should always end with a UTF-8 encoded close curly"); }
public void NestedResources_symbols_are_bound() { 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 sibling 'childType@2020-01-02' = { name: 'sibling' properties: { style: child.properties.style size: parent.properties.size } } } "; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(program)); var model = compilation.GetEntrypointSemanticModel(); model.GetAllDiagnostics().Should().BeEmpty(); var expected = new [] { new { name = "child", type = "My.RP/parentType/childType@2020-01-01", }, new { name = "parent", type = "My.RP/parentType@2020-01-01", }, new { name = "sibling", type = "My.RP/parentType/childType@2020-01-02", }, }; 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 ShouldConvertExpressionsCorrectly(string text, string expected) { var programText = $"var test = {text}"; var compilation = new Compilation(TestResourceTypeProvider.Create(), SyntaxTreeGroupingFactory.CreateFromText(programText)); var programSyntax = compilation.SyntaxTreeGrouping.EntryPoint.ProgramSyntax; var variableDeclarationSyntax = programSyntax.Children.OfType <VariableDeclarationSyntax>().First(); var converter = new ExpressionConverter(new EmitterContext(compilation.GetEntrypointSemanticModel())); var converted = converter.ConvertExpression(variableDeclarationSyntax.Value); var serializer = new ExpressionSerializer(new ExpressionSerializerSettings { IncludeOuterSquareBrackets = true, SingleStringHandling = ExpressionSerializerSingleStringHandling.SerializeAsString }); var actual = serializer.SerializeExpression(converted); actual.Should().Be(expected); }
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 EndOfFileFollowingSpaceAfterParameterKeyWordShouldNotThrow() { var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), SyntaxTreeGroupingFactory.CreateFromText("parameter ")); compilation.GetEntrypointSemanticModel().GetParseDiagnostics(); }
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 DeclarationContextShouldReturnKeywordCompletions() { var grouping = SyntaxTreeGroupingFactory.CreateFromText(string.Empty); var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), grouping); compilation.GetEntrypointSemanticModel().GetAllDiagnostics().Should().BeEmpty(); var provider = new BicepCompletionProvider(new FileResolver(), new SnippetsProvider(), new TelemetryProvider(Server)); 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 !.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 !.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 !.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 !.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 !.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 !.TextEdit !.NewText.Should().Be("var"); }); }
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 !];