forked from Azure/bicep
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBicepDeploymentGraphHandler.cs
156 lines (124 loc) · 7.2 KB
/
BicepDeploymentGraphHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Bicep.Core;
using Bicep.Core.Diagnostics;
using Bicep.Core.Emit;
using Bicep.Core.Parsing;
using Bicep.Core.Semantics;
using Bicep.Core.Syntax;
using Bicep.LanguageServer.CompilationManager;
using Bicep.LanguageServer.Extensions;
using MediatR;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions.JsonRpc;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
namespace Bicep.LanguageServer.Handlers
{
[Method("textDocument/deploymentGraph", Direction.ClientToServer)]
public record BicepDeploymentGraphParams(TextDocumentIdentifier TextDocument) : ITextDocumentIdentifierParams, IRequest<BicepDeploymentGraph?>;
public record BicepDeploymentGraph(IEnumerable<BicepDeploymentGraphNode> Nodes, IEnumerable<BicepDeploymentGraphEdge> Edges, int ErrorCount);
public record BicepDeploymentGraphNode(string Id, string Type, bool IsCollection, Range Range, bool HasChildren, bool HasError, string? FilePath);
public record BicepDeploymentGraphEdge(string SourceId, string TargetId);
public class BicepDeploymentGraphHandler : IJsonRpcRequestHandler<BicepDeploymentGraphParams, BicepDeploymentGraph?>
{
private readonly ILogger<BicepDocumentSymbolHandler> logger;
private readonly ICompilationManager compilationManager;
public BicepDeploymentGraphHandler(ILogger<BicepDocumentSymbolHandler> logger, ICompilationManager compilationManager)
{
this.logger = logger;
this.compilationManager = compilationManager;
}
public Task<BicepDeploymentGraph?> Handle(BicepDeploymentGraphParams request, CancellationToken cancellationToken)
{
CompilationContext? context = this.compilationManager.GetCompilation(request.TextDocument.Uri);
if (context == null)
{
this.logger.LogError("Dependency graph request arrived before file {Uri} could be compiled.", request.TextDocument.Uri);
return Task.FromResult<BicepDeploymentGraph?>(null);
}
var graph = CreateDeploymentGraph(context, Path.GetFullPath(request.TextDocument.Uri.GetFileSystemPath()));
return Task.FromResult<BicepDeploymentGraph?>(graph);
}
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, new ResourceDependencyVisitor.Options() { IncludeExisting = true })
.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()?.FormatType() ?? "<unknown>";
var isCollection = resourceSymbol.IsCollection;
var resourceSpan = resourceSymbol.DeclaringResource.Span;
var range = resourceSpan.ToRange(semanticModel.SourceFile.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(semanticModel.SourceFile.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, filePath);
}
}
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.Where(d => d.Kind == ResourceDependencyKind.Primary))
{
if (!nodesBySymbol.TryGetValue(dependency.Resource, out var target))
{
continue;
}
edges.Add(new BicepDeploymentGraphEdge(source.Id, target.Id));
}
}
}
var graph = new BicepDeploymentGraph(
nodes.OrderBy(node => node.Id),
edges.OrderBy(edge => $"{edge.SourceId}>{edge.TargetId}"),
entrySemanticModel.GetAllDiagnostics().Count(x => x.Level == DiagnosticLevel.Error));
return graph;
}
}
}