示例#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();
                    }
                }
            }
        }
 public override void VisitPropertyAccessSyntax(PropertyAccessSyntax syntax)
 {
     base.VisitPropertyAccessSyntax(syntax);
     if (this.errorSyntax != null && TextSpan.AreOverlapping(this.errorSyntax, syntax))
     {
         // Due to the nature of visitPropertyAccessSyntax, we have to propagate the
         // nested errorSyntax up the stack.
         this.errorSyntax = syntax;
     }
     else if (syntax.BaseExpression is VariableAccessSyntax variableAccessSyntax)
     {
         // This is a non-overlapping error, which means that there's two or more runtime properties being referenced
         if (this.errorSyntax != null)
         {
             this.AppendError();
         }
         if (ExtractResourceOrModuleSymbolAndBodyObj(this.model, variableAccessSyntax) is ({ } declaredSymbol, { } referencedBodyObj) &&
             referencedBodyObj.Properties.TryGetValue(syntax.PropertyName.IdentifierName, out var propertyType) &&
             !propertyType.Flags.HasFlag(TypePropertyFlags.DeployTimeConstant))
         {
             this.errorSyntax       = syntax;
             this.accessedSymbol    = declaredSymbol.Name;
             this.referencedBodyObj = referencedBodyObj;
         }
     }
 }
示例#3
0
        public void AreOverlapping_ShouldDetermineOverlapCorrectly(string firstSpan, string secondSpan, bool expectedOverlapResult)
        {
            var first  = TextSpan.Parse(firstSpan);
            var second = TextSpan.Parse(secondSpan);

            TextSpan.AreOverlapping(first, second).Should().Be(expectedOverlapResult);
            TextSpan.AreOverlapping(second, first).Should().Be(expectedOverlapResult);
        }
        public override void VisitPropertyAccessSyntax(PropertyAccessSyntax syntax)
        {
            base.VisitPropertyAccessSyntax(syntax);
            if (this.errorSyntax != null && TextSpan.AreOverlapping(this.errorSyntax, syntax))
            {
                // Due to the nature of visitPropertyAccessSyntax, we have to propagate the
                // nested errorSyntax up the stack.
                this.errorSyntax = syntax;
            }
            else
            {
                switch (syntax.BaseExpression)
                {
                case VariableAccessSyntax variableAccessSyntax:
                {
                    // This is a non-overlapping error, which means that there's two or more runtime properties being referenced
                    if (this.errorSyntax != null)
                    {
                        this.AppendError();
                    }

                    if (ExtractResourceOrModuleSymbolAndBodyType(this.model, variableAccessSyntax) is ({ } referencedSymbol, { } referencedBodyType))
                    {
                        SetState(syntax, referencedSymbol, referencedBodyType, syntax.PropertyName.IdentifierName);
                    }

                    break;
                }

                case ArrayAccessSyntax {
                        BaseExpression: VariableAccessSyntax baseVariableAccess
                } :
                    {
                        if (this.errorSyntax != null)
                        {
                            this.AppendError();
                        }

                        if (ExtractResourceOrModuleCollectionSymbolAndBodyType(this.model, baseVariableAccess) is ({ } referencedSymbol, { } referencedBodyType))
                        {
                            SetState(syntax, referencedSymbol, referencedBodyType, syntax.PropertyName.IdentifierName);
                        }

                        break;
                    }
                }
            }
        // these need to be kept synchronized.
        public override void VisitArrayAccessSyntax(ArrayAccessSyntax syntax)
        {
            base.VisitArrayAccessSyntax(syntax);
            if (this.errorSyntax != null && TextSpan.AreOverlapping(this.errorSyntax, syntax))
            {
                // Due to the nature of visitPropertyAccessSyntax, we have to propagate the
                // nested errorSyntax up the stack.
                this.errorSyntax = syntax;
            }
            else if (syntax.BaseExpression is VariableAccessSyntax variableAccessSyntax)
            {
                // This is a non-overlapping error, which means that there's two or more runtime properties being referenced
                if (this.errorSyntax != null)
                {
                    this.AppendError();
                }

                // validate only on resource and module symbols
                if (ExtractResourceOrModuleSymbolAndBodyObj(this.model, variableAccessSyntax) is ({ } declaredSymbol, { } referencedBodyObj))
                {
                    switch (syntax.IndexExpression)
                    {
                    case StringSyntax stringSyntax when stringSyntax.TryGetLiteralValue() is string literalValue:
                        if (referencedBodyObj.Properties.TryGetValue(literalValue, out var propertyType) &&
                            !propertyType.Flags.HasFlag(TypePropertyFlags.DeployTimeConstant))
                        {
                            this.errorSyntax       = syntax;
                            this.accessedSymbol    = declaredSymbol.Name;
                            this.referencedBodyObj = referencedBodyObj;
                        }

                        break;

                    default:
                        // we will block referencing module and resource properties using string interpolation and number indexing
                        this.errorSyntax       = syntax;
                        this.accessedSymbol    = declaredSymbol !.Name;
                        this.referencedBodyObj = referencedBodyObj;
                        break;
                    }
                }
            }
        }
示例#6
0
        public static BicepDeploymentGraph CreateDeploymentGraph(CompilationContext context, string entryFilePath)
        {
            var nodes = new List <BicepDeploymentGraphNode>();
            var edges = new List <BicepDeploymentGraphEdge>();

            var queue = new Queue <(SemanticModel, string, string?)>();
            var entrySemanticModel = context.Compilation.GetEntrypointSemanticModel();

            queue.Enqueue((entrySemanticModel, entryFilePath, null));

            while (queue.Count > 0)
            {
                var(semanticModel, filePath, parentId) = queue.Dequeue();
                var nodesBySymbol        = new Dictionary <DeclaredSymbol, BicepDeploymentGraphNode>();
                var dependenciesBySymbol = ResourceDependencyVisitor.GetResourceDependencies(semanticModel)
                                           .Where(x => x.Key.Name != LanguageConstants.MissingName && x.Key.Name != LanguageConstants.ErrorName)
                                           .ToImmutableDictionary(x => x.Key, x => x.Value);

                var errors = semanticModel.GetAllDiagnostics().Where(x => x.Level == DiagnosticLevel.Error).ToList();

                // Create nodes.
                foreach (var symbol in dependenciesBySymbol.Keys)
                {
                    var id = parentId is null ? symbol.Name : $"{parentId}::{symbol.Name}";

                    if (symbol is ResourceSymbol resourceSymbol)
                    {
                        var resourceType     = resourceSymbol.TryGetResourceTypeReference()?.FullyQualifiedType ?? "<unknown>";
                        var isCollection     = resourceSymbol.IsCollection;
                        var resourceSpan     = resourceSymbol.DeclaringResource.Span;
                        var range            = resourceSpan.ToRange(context.LineStarts);
                        var resourceHasError = errors.Any(error => TextSpan.AreOverlapping(resourceSpan, error.Span));

                        nodesBySymbol[symbol] = new BicepDeploymentGraphNode(id, resourceType, isCollection, range, false, resourceHasError, filePath);
                    }

                    if (symbol is ModuleSymbol moduleSymbol)
                    {
                        var directory          = Path.GetDirectoryName(filePath);
                        var moduleRelativePath = moduleSymbol.DeclaringModule.TryGetPath()?.TryGetLiteralValue();
                        var moduleFilePath     = directory is not null && moduleRelativePath is not null
                            ? Path.GetFullPath(Path.Combine(directory, moduleRelativePath))
                            : null;

                        var isCollection   = moduleSymbol.IsCollection;
                        var moduleSpan     = moduleSymbol.DeclaringModule.Span;
                        var range          = moduleSpan.ToRange(context.LineStarts);
                        var moduleHasError = errors.Any(error => TextSpan.AreOverlapping(moduleSpan, error.Span));

                        var hasChildren = false;

                        if (moduleFilePath is not null &&
                            moduleSymbol.TryGetSemanticModel(out var moduleSemanticModel, out var _) &&
                            moduleSemanticModel is SemanticModel bicepModel &&
                            (bicepModel.Root.ResourceDeclarations.Any() || bicepModel.Root.ModuleDeclarations.Any()))
                        {
                            hasChildren = true;
                            queue.Enqueue((bicepModel, moduleFilePath, id));
                        }

                        nodesBySymbol[symbol] = new BicepDeploymentGraphNode(id, "<module>", isCollection, range, hasChildren, moduleHasError, moduleFilePath);
                    }
                }

                nodes.AddRange(nodesBySymbol.Values);

                // Create edges.
                foreach (var(symbol, dependencies) in dependenciesBySymbol)
                {
                    if (!nodesBySymbol.TryGetValue(symbol, out var source))
                    {
                        continue;
                    }

                    foreach (var dependency in dependencies)
                    {
                        if (!nodesBySymbol.TryGetValue(dependency.Resource, out var target))
                        {
                            continue;
                        }

                        edges.Add(new BicepDeploymentGraphEdge(source.Id, target.Id));
                    }
                }
            }

            return(new BicepDeploymentGraph(
                       nodes.OrderBy(node => node.Id),
                       edges.OrderBy(edge => $"{edge.SourceId}>{edge.TargetId}"),
                       entrySemanticModel.GetAllDiagnostics().Count(x => x.Level == DiagnosticLevel.Error)));
        }