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; } } }
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; } } } }
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))); }