Exemple #1
0
        public async Task Overlapping_tokens_are_not_returned(DataSet dataSet)
        {
            var uri       = DocumentUri.From($"/{dataSet.Name}");
            var bicepFile = SourceFileFactory.CreateBicepFile(uri.ToUri(), dataSet.Bicep);

            using var helper = await LanguageServerHelper.StartServerWithTextAsync(TestContext, dataSet.Bicep, uri);

            var client = helper.Client;

            var semanticTokens = await client.TextDocument.RequestSemanticTokens(new SemanticTokensParams
            {
                TextDocument = new TextDocumentIdentifier(uri),
            });

            var tokenSpans = CalculateTokenTextSpans(bicepFile.LineStarts, semanticTokens !.Data).ToArray();

            for (var i = 1; i < tokenSpans.Length; i++)
            {
                var currentSpan = tokenSpans[i];
                var prevSpan    = tokenSpans[i - 1];

                if (TextSpan.AreOverlapping(prevSpan, currentSpan))
                {
                    using (new AssertionScope()
                           .WithAnnotations(bicepFile, "overlapping tokens", new[] { prevSpan, currentSpan }, _ => "here", x => x.ToRange(bicepFile.LineStarts)))
                    {
                        TextSpan.AreOverlapping(prevSpan, currentSpan).Should().BeFalse();
                    }
                }
            }
        }
Exemple #2
0
        public async Task VerifyNestedResourceCompletionReturnsCustomSnippetWithoutParentInformation()
        {
            string fileWithCursors = @"resource automationAccount 'Microsoft.Automation/automationAccounts@2019-06-01' = {
  name: 'name'
  location: resourceGroup().location

  |
}";

            var(file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
            var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///path/to/main.bicep"), file);
            var client    = await IntegrationTestHelper.StartServerWithTextAsync(TestContext, file, bicepFile.FileUri, resourceTypeProvider : TypeProvider);

            var completionLists = await RequestCompletions(client, bicepFile, cursors);

            completionLists.Count().Should().Be(1);

            var snippetCompletion = completionLists.First() !.Items.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == "res-automation-cred");

            snippetCompletion.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("res-automation-cred");
                c.Detail.Should().Be("Automation Credential");
                c.InsertTextFormat.Should().Be(InsertTextFormat.Snippet);
                c.TextEdit?.TextEdit?.NewText?.Should().BeEquivalentToIgnoringNewlines(@"resource ${2:automationCredential} 'credentials@2019-06-01' = {
  name: ${3:'name'}
  properties: {
    userName: ${4:'userName'}
    password: ${5:'password'}
    description: ${6:'description'}
  }
}");
            });
        }
Exemple #3
0
        private bool RewriteSyntax(Workspace workspace, Uri entryUri, Func <SemanticModel, SyntaxRewriteVisitor> rewriteVisitorBuilder)
        {
            var hasChanges         = false;
            var dispatcher         = new ModuleDispatcher(this.registryProvider);
            var configuration      = configurationManager.GetBuiltInConfiguration(disableAnalyzers: true);
            var linterAnalyzer     = new LinterAnalyzer(configuration);
            var sourceFileGrouping = SourceFileGroupingBuilder.Build(fileResolver, dispatcher, workspace, entryUri, configuration);
            var compilation        = new Compilation(this.features, namespaceProvider, sourceFileGrouping, configuration, linterAnalyzer);

            // force enumeration here with .ToImmutableArray() as we're going to be modifying the sourceFileGrouping collection as we iterate
            var fileUris = sourceFileGrouping.SourceFiles.Select(x => x.FileUri).ToImmutableArray();

            foreach (var fileUri in fileUris)
            {
                if (sourceFileGrouping.SourceFiles.FirstOrDefault(x => x.FileUri == fileUri) is not BicepFile bicepFile)
                {
                    throw new InvalidOperationException($"Failed to find a bicep source file for URI {fileUri}.");
                }

                var newProgramSyntax = rewriteVisitorBuilder(compilation.GetSemanticModel(bicepFile)).Rewrite(bicepFile.ProgramSyntax);

                if (!object.ReferenceEquals(bicepFile.ProgramSyntax, newProgramSyntax))
                {
                    hasChanges = true;
                    var newFile = SourceFileFactory.CreateBicepFile(fileUri, newProgramSyntax.ToText());
                    workspace.UpsertSourceFile(newFile);

                    sourceFileGrouping = SourceFileGroupingBuilder.Build(fileResolver, dispatcher, workspace, entryUri, configuration);
                    compilation        = new Compilation(this.features, namespaceProvider, sourceFileGrouping, configuration, linterAnalyzer);
                }
            }

            return(hasChanges);
        }
Exemple #4
0
        private async Task <string> RequestSnippetCompletion(string bicepFileName, CompletionData completionData, string placeholderFile, int cursor)
        {
            var documentUri = DocumentUri.FromFileSystemPath(bicepFileName);
            var bicepFile   = SourceFileFactory.CreateBicepFile(documentUri.ToUri(), placeholderFile);

            var client = await IntegrationTestHelper.StartServerWithTextAsync(
                this.TestContext,
                placeholderFile,
                documentUri,
                null,
                TypeProvider);

            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = documentUri,
                Position     = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, cursor),
            });

            var matchingSnippets = completions.Where(x => x.Kind == CompletionItemKind.Snippet && x.Label == completionData.Prefix);

            matchingSnippets.Should().HaveCount(1);
            var completion = matchingSnippets.First();

            completion.TextEdit.Should().NotBeNull();
            completion.TextEdit !.TextEdit !.Range.Should().Be(new TextSpan(cursor, 0).ToRange(bicepFile.LineStarts));
            completion.TextEdit.TextEdit.NewText.Should().NotBeNullOrWhiteSpace();

            return(completion.TextEdit.TextEdit.NewText);
        }
Exemple #5
0
        private static async Task RunDefinitionScenarioTest(TestContext testContext, string fileWithCursors, Action <List <LocationOrLocationLinks> > assertAction)
        {
            var(file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
            var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///path/to/main.bicep"), file);

            var client = await IntegrationTestHelper.StartServerWithTextAsync(testContext, file, bicepFile.FileUri, creationOptions : new LanguageServer.Server.CreationOptions(NamespaceProvider: BuiltInTestTypes.Create()));

            var results = await RequestDefinitions(client, bicepFile, cursors);

            assertAction(results);
        }
Exemple #6
0
        public static (BicepFile bicepFile, bool hasChanges) Rewrite(Compilation prevCompilation, BicepFile bicepFile, Func <SemanticModel, SyntaxRewriteVisitor> rewriteVisitorBuilder)
        {
            var semanticModel    = new SemanticModel(prevCompilation, bicepFile, prevCompilation.SourceFileGrouping.FileResolver, prevCompilation.Configuration);
            var newProgramSyntax = rewriteVisitorBuilder(semanticModel).Rewrite(bicepFile.ProgramSyntax);

            if (object.ReferenceEquals(bicepFile.ProgramSyntax, newProgramSyntax))
            {
                return(bicepFile, false);
            }

            bicepFile = SourceFileFactory.CreateBicepFile(bicepFile.FileUri, newProgramSyntax.ToTextPreserveFormatting());
            return(bicepFile, true);
        }
Exemple #7
0
        public async Task VerifyNestedResourceBodyCompletionReturnsSnippets()
        {
            string fileWithCursors = @"resource automationAccount 'Microsoft.Automation/automationAccounts@2019-06-01' = {
  name: 'name'
  location: resourceGroup().location

  resource automationCredential 'credentials@2019-06-01' = |
}";

            var(file, cursors) = ParserHelper.GetFileWithCursors(fileWithCursors);
            var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///path/to/main.bicep"), file);
            var client    = await IntegrationTestHelper.StartServerWithTextAsync(TestContext, file, bicepFile.FileUri, resourceTypeProvider : TypeProvider);

            var completionLists = await RequestCompletions(client, bicepFile, cursors);

            completionLists.Count().Should().Be(1);

            var snippetCompletions = completionLists.First() !.Items.Where(x => x.Kind == CompletionItemKind.Snippet);

            snippetCompletions.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("{}");
            },
                c =>
            {
                c.Label.Should().Be("snippet");
            },
                c =>
            {
                c.Label.Should().Be("required-properties");
            },
                c =>
            {
                c.Label.Should().Be("if");
            },
                c =>
            {
                c.Label.Should().Be("for");
            },
                c =>
            {
                c.Label.Should().Be("for-indexed");
            },
                c =>
            {
                c.Label.Should().Be("for-filtered");
            });
        }
Exemple #8
0
        public (Uri, ImmutableDictionary <Uri, string>) Decompile(string inputPath, string outputPath)
        {
            inputPath = PathHelper.ResolvePath(inputPath);
            Uri inputUri = PathHelper.FilePathToFileUrl(inputPath);

            Uri outputUri = PathHelper.FilePathToFileUrl(outputPath);

            var decompilation = TemplateDecompiler.DecompileFileWithModules(invocationContext.ResourceTypeProvider, new FileResolver(), inputUri, outputUri);

            foreach (var(fileUri, bicepOutput) in decompilation.filesToSave)
            {
                workspace.UpsertSourceFile(SourceFileFactory.CreateBicepFile(fileUri, bicepOutput));
            }

            _ = Compile(decompilation.entrypointUri.AbsolutePath); // to verify success we recompile and check for syntax errors.

            return(decompilation);
        }
Exemple #9
0
        public async Task VerifyResourceBodyCompletionWithoutExistingKeywordIncludesCustomSnippet()
        {
            string text = @"resource aksCluster 'Microsoft.ContainerService/managedClusters@2021-03-01' = ";

            var bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///main.bicep"), text);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, text, bicepFile.FileUri, resourceTypeProvider : TypeProvider);

            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = new TextDocumentIdentifier(bicepFile.FileUri),
                Position     = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, text.Length),
            });

            completions.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("{}");
            },
                c =>
            {
                c.Label.Should().Be("snippet");
            },
                c =>
            {
                c.Label.Should().Be("required-properties");
            },
                c =>
            {
                c.Label.Should().Be("if");
            },
                c =>
            {
                c.Label.Should().Be("for");
            },
                c =>
            {
                c.Label.Should().Be("for-indexed");
            },
                c =>
            {
                c.Label.Should().Be("for-filtered");
            });
        }
Exemple #10
0
        public async Task <(Uri, ImmutableDictionary <Uri, string>)> DecompileAsync(string inputPath, string outputPath)
        {
            inputPath = PathHelper.ResolvePath(inputPath);
            Uri inputUri = PathHelper.FilePathToFileUrl(inputPath);

            Uri outputUri = PathHelper.FilePathToFileUrl(outputPath);

            var decompilation = decompiler.DecompileFileWithModules(inputUri, outputUri);

            foreach (var(fileUri, bicepOutput) in decompilation.filesToSave)
            {
                workspace.UpsertSourceFile(SourceFileFactory.CreateBicepFile(fileUri, bicepOutput));
            }

            // to verify success we recompile and check for syntax errors.
            await CompileAsync(decompilation.entrypointUri.AbsolutePath, skipRestore : true);

            return(decompilation);
        }
Exemple #11
0
        public (Uri entrypointUri, ImmutableDictionary <Uri, string> filesToSave) DecompileFileWithModules(Uri entryJsonUri, Uri entryBicepUri)
        {
            var workspace      = new Workspace();
            var decompileQueue = new Queue <(Uri, Uri)>();

            decompileQueue.Enqueue((entryJsonUri, entryBicepUri));

            while (decompileQueue.Count > 0)
            {
                var(jsonUri, bicepUri) = decompileQueue.Dequeue();

                if (PathHelper.HasBicepExtension(jsonUri))
                {
                    throw new InvalidOperationException($"Cannot decompile the file with .bicep extension: {jsonUri}.");
                }

                if (workspace.TryGetSourceFile(bicepUri, out _))
                {
                    continue;
                }

                if (!fileResolver.TryRead(jsonUri, out var jsonInput, out _))
                {
                    throw new InvalidOperationException($"Failed to read {jsonUri}");
                }

                var(program, jsonTemplateUrisByModule) = TemplateConverter.DecompileTemplate(workspace, fileResolver, bicepUri, jsonInput);
                var bicepFile = SourceFileFactory.CreateBicepFile(bicepUri, program.ToText());
                workspace.UpsertSourceFile(bicepFile);

                foreach (var module in program.Children.OfType <ModuleDeclarationSyntax>())
                {
                    var moduleRelativePath = SyntaxHelper.TryGetModulePath(module, out _);
                    if (moduleRelativePath == null ||
                        !LocalModuleReference.Validate(moduleRelativePath, out _) ||
                        !Uri.TryCreate(bicepUri, moduleRelativePath, out var moduleUri))
                    {
                        // Do our best, but keep going if we fail to resolve a module file
                        continue;
                    }

                    if (!workspace.TryGetSourceFile(moduleUri, out _) && jsonTemplateUrisByModule.TryGetValue(module, out var linkedTemplateUri))
                    {
                        decompileQueue.Enqueue((linkedTemplateUri, moduleUri));
                    }
                }
            }

            RewriteSyntax(workspace, entryBicepUri, semanticModel => new ParentChildResourceNameRewriter(semanticModel));
            RewriteSyntax(workspace, entryBicepUri, semanticModel => new DependsOnRemovalRewriter(semanticModel));
            RewriteSyntax(workspace, entryBicepUri, semanticModel => new ForExpressionSimplifierRewriter(semanticModel));
            for (var i = 0; i < 5; i++)
            {
                // This is a little weird. If there are casing issues nested inside casing issues (e.g. in an object), then the inner casing issue will have no type information
                // available, as the compilation will not have associated a type with it (since there was no match on the outer object). So we need to correct the outer issue first,
                // and then move to the inner one. We need to recompute the entire compilation to do this. It feels simpler to just do this in passes over the file, rather than on demand.
                if (!RewriteSyntax(workspace, entryBicepUri, semanticModel => new TypeCasingFixerRewriter(semanticModel)))
                {
                    break;
                }
            }

            return(entryBicepUri, PrintFiles(workspace));
        }
Exemple #12
0
        public async Task VerifyResourceBodyCompletionWithDiscriminatedObjectTypeContainsRequiredPropertiesSnippet()
        {
            string text      = @"resource deploymentScripts 'Microsoft.Resources/deploymentScripts@2020-10-01'=";
            var    bicepFile = SourceFileFactory.CreateBicepFile(new Uri("file:///main.bicep"), text);

            using var client = await IntegrationTestHelper.StartServerWithTextAsync(this.TestContext, text, bicepFile.FileUri, resourceTypeProvider : TypeProvider);

            var completions = await client.RequestCompletion(new CompletionParams
            {
                TextDocument = new TextDocumentIdentifier(bicepFile.FileUri),
                Position     = TextCoordinateConverter.GetPosition(bicepFile.LineStarts, text.Length),
            });

            completions.Should().SatisfyRespectively(
                c =>
            {
                c.Label.Should().Be("{}");
            },
                c =>
            {
                c.InsertTextFormat.Should().Be(InsertTextFormat.Snippet);
                c.Label.Should().Be("required-properties-AzureCLI");
                c.Detail.Should().Be("Required properties");
                c.TextEdit?.TextEdit?.NewText?.Should().BeEquivalentToIgnoringNewlines(@"{
	name: $1
	location: $2
	kind: 'AzureCLI'
	properties: {
		azCliVersion: $3
		retentionInterval: $4
	}
}$0");
            },
                c =>
            {
                c.Label.Should().Be("required-properties-AzurePowerShell");
                c.Detail.Should().Be("Required properties");
                c.TextEdit?.TextEdit?.NewText?.Should().BeEquivalentToIgnoringNewlines(@"{
	name: $1
	location: $2
	kind: 'AzurePowerShell'
	properties: {
		azPowerShellVersion: $3
		retentionInterval: $4
	}
}$0");
            },
                c =>
            {
                c.Label.Should().Be("if");
            },
                c =>
            {
                c.Label.Should().Be("for");
            },
                c =>
            {
                c.Label.Should().Be("for-indexed");
            },
                c =>
            {
                c.Label.Should().Be("for-filtered");
            });
        }
        private static ModuleDeclarationSyntax CreateModule(string reference)
        {
            var file = SourceFileFactory.CreateBicepFile(new System.Uri("untitled://hello"), $"module foo '{reference}' = {{}}");

            return(file.ProgramSyntax.Declarations.OfType <ModuleDeclarationSyntax>().Single());
        }