Beispiel #1
0
        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 [] {
Beispiel #2
0
        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();
        }
Beispiel #4
0
        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|   }
");
        }
Beispiel #6
0
        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());
        }
Beispiel #7
0
        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();
        }
Beispiel #8
0
        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();
        }
Beispiel #9
0
        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.");
        }
Beispiel #11
0
        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 !];
Beispiel #12
0
        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();
        }
Beispiel #14
0
        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);
        }
Beispiel #15
0
        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 [] {
Beispiel #18
0
        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);
        }
Beispiel #23
0
        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");
        }
Beispiel #24
0
        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);
        }
Beispiel #25
0
        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);
        }
Beispiel #26
0
        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);
        }
Beispiel #27
0
        public void EndOfFileFollowingSpaceAfterParameterKeyWordShouldNotThrow()
        {
            var compilation = new Compilation(TestTypeHelper.CreateEmptyProvider(), SyntaxTreeGroupingFactory.CreateFromText("parameter "));

            compilation.GetEntrypointSemanticModel().GetParseDiagnostics();
        }
Beispiel #28
0
        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 !];