Skip to content

Commit dea497e

Browse files
committed
Automatically add projects if they don't exist when updating
1 parent 2f71d65 commit dea497e

File tree

7 files changed

+503
-255
lines changed

7 files changed

+503
-255
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/ProjectConfigurationFileChangeEventArgs.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.Razor.ProjectSystem;
99
using Microsoft.AspNetCore.Razor.Utilities;
1010
using Microsoft.CodeAnalysis.Razor;
11+
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
1112
using Microsoft.CodeAnalysis.Razor.Workspaces;
1213

1314
namespace Microsoft.AspNetCore.Razor.LanguageServer;
@@ -77,4 +78,10 @@ public bool TryDeserialize(LanguageServerFeatureOptions options, [NotNullWhen(tr
7778

7879
return projectInfo is not null;
7980
}
81+
82+
internal ProjectKey GetProjectKey()
83+
{
84+
var intermediateOutputPath = FilePathNormalizer.GetNormalizedDirectoryName(ConfigurationFilePath);
85+
return ProjectKey.FromString(intermediateOutputPath);
86+
}
8087
}

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/ProjectConfigurationStateSynchronizer.Comparer.cs

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using Microsoft.CodeAnalysis.Razor;
6+
using Microsoft.Extensions.Internal;
67

78
namespace Microsoft.AspNetCore.Razor.LanguageServer;
89

@@ -17,42 +18,15 @@ private Comparer()
1718
}
1819

1920
public bool Equals(Work? x, Work? y)
20-
{
21-
if (x is null)
22-
{
23-
return y is null;
24-
}
25-
else if (y is null)
26-
{
27-
return x is null;
28-
}
29-
30-
// For purposes of removing duplicates from batches, two Work instances
31-
// are equal only if their identifying properties are equal. So, only
32-
// configuration file paths and project keys.
33-
34-
if (!FilePathComparer.Instance.Equals(x.ConfigurationFilePath, y.ConfigurationFilePath))
35-
{
36-
return false;
37-
}
38-
39-
return (x, y) switch
40-
{
41-
(AddProject, AddProject) => true,
21+
=> (x, y) switch
22+
{
23+
(Work(var keyX), Work(var keyY)) => keyX == keyY,
24+
(null, null) => true,
4225

43-
(ResetProject { ProjectKey: var keyX },
44-
ResetProject { ProjectKey: var keyY })
45-
=> keyX == keyY,
46-
47-
(UpdateProject { ProjectKey: var keyX },
48-
UpdateProject { ProjectKey: var keyY })
49-
=> keyX == keyY,
50-
51-
_ => false,
52-
};
53-
}
26+
_ => false
27+
};
5428

5529
public int GetHashCode(Work obj)
56-
=> obj.GetHashCode();
30+
=> obj.ProjectKey.GetHashCode();
5731
}
5832
}

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/ProjectConfigurationStateSynchronizer.cs

Lines changed: 35 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,26 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
7-
using System.IO;
7+
using System.Diagnostics;
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
1111
using Microsoft.AspNetCore.Razor.LanguageServer.ProjectSystem;
12+
using Microsoft.AspNetCore.Razor.PooledObjects;
1213
using Microsoft.AspNetCore.Razor.ProjectSystem;
1314
using Microsoft.AspNetCore.Razor.Utilities;
14-
using Microsoft.CodeAnalysis;
15-
using Microsoft.CodeAnalysis.Razor;
1615
using Microsoft.CodeAnalysis.Razor.Logging;
1716
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
1817
using Microsoft.CodeAnalysis.Razor.Utilities;
1918
using Microsoft.CodeAnalysis.Razor.Workspaces;
20-
using Microsoft.VisualStudio.Threading;
2119

2220
namespace Microsoft.AspNetCore.Razor.LanguageServer;
2321

2422
internal partial class ProjectConfigurationStateSynchronizer : IProjectConfigurationFileChangeListener, IDisposable
2523
{
26-
private abstract record Work(string ConfigurationFilePath);
27-
private sealed record AddProject(string ConfigurationFilePath, RazorProjectInfo ProjectInfo) : Work(ConfigurationFilePath);
28-
private sealed record ResetProject(string ConfigurationFilePath, ProjectKey ProjectKey) : Work(ConfigurationFilePath);
29-
private sealed record UpdateProject(string ConfigurationFilePath, ProjectKey ProjectKey, RazorProjectInfo ProjectInfo) : Work(ConfigurationFilePath);
24+
private abstract record Work(ProjectKey ProjectKey);
25+
private sealed record ResetProject(ProjectKey ProjectKey) : Work(ProjectKey);
26+
private sealed record UpdateProject(ProjectKey ProjectKey, RazorProjectInfo ProjectInfo) : Work(ProjectKey);
3027

3128
private static readonly TimeSpan s_delay = TimeSpan.FromMilliseconds(250);
3229

@@ -37,8 +34,7 @@ private sealed record UpdateProject(string ConfigurationFilePath, ProjectKey Pro
3734
private readonly CancellationTokenSource _disposeTokenSource;
3835
private readonly AsyncBatchingWorkQueue<Work> _workQueue;
3936

40-
private ImmutableDictionary<string, ProjectKey> _filePathToProjectKeyMap =
41-
ImmutableDictionary<string, ProjectKey>.Empty.WithComparers(keyComparer: FilePathComparer.Instance);
37+
private readonly Dictionary<ProjectKey, ResetProject> _resetProjectMap = new();
4238

4339
public ProjectConfigurationStateSynchronizer(
4440
IRazorProjectService projectService,
@@ -67,44 +63,26 @@ public void Dispose()
6763
_disposeTokenSource.Cancel();
6864
_disposeTokenSource.Dispose();
6965
}
66+
7067
private async ValueTask ProcessBatchAsync(ImmutableArray<Work> items, CancellationToken token)
7168
{
7269
foreach (var item in items.GetMostRecentUniqueItems(Comparer.Instance))
7370
{
71+
if (token.IsCancellationRequested)
72+
{
73+
return;
74+
}
75+
7476
var itemTask = item switch
7577
{
76-
AddProject(var configurationFilePath, var projectInfo) => AddProjectAsync(configurationFilePath, projectInfo, token),
77-
ResetProject(_, var projectKey) => ResetProjectAsync(projectKey, token),
78-
UpdateProject(_, var projectKey, var projectInfo) => UpdateProjectAsync(projectKey, projectInfo, token),
78+
ResetProject(var projectKey) => ResetProjectAsync(projectKey, token),
79+
UpdateProject(var projectKey, var projectInfo) => UpdateProjectAsync(projectKey, projectInfo, token),
7980
_ => Assumed.Unreachable<Task>()
8081
};
8182

8283
await itemTask.ConfigureAwait(false);
8384
}
8485

85-
async Task AddProjectAsync(string configurationFilePath, RazorProjectInfo projectInfo, CancellationToken token)
86-
{
87-
var projectFilePath = FilePathNormalizer.Normalize(projectInfo.FilePath);
88-
var intermediateOutputPath = FilePathNormalizer.GetNormalizedDirectoryName(configurationFilePath);
89-
90-
var projectKey = await _projectService
91-
.AddProjectAsync(
92-
projectFilePath,
93-
intermediateOutputPath,
94-
projectInfo.Configuration,
95-
projectInfo.RootNamespace,
96-
projectInfo.DisplayName,
97-
token)
98-
.ConfigureAwait(false);
99-
100-
_logger.LogInformation($"Added {projectKey.Id}.");
101-
102-
ImmutableInterlocked.AddOrUpdate(ref _filePathToProjectKeyMap, configurationFilePath, projectKey, static (k, v) => v);
103-
_logger.LogInformation($"Project configuration file added for project '{projectFilePath}': '{configurationFilePath}'");
104-
105-
await UpdateProjectAsync(projectKey, projectInfo, token).ConfigureAwait(false);
106-
}
107-
10886
Task ResetProjectAsync(ProjectKey projectKey, CancellationToken token)
10987
{
11088
_logger.LogInformation($"Resetting {projectKey.Id}.");
@@ -125,8 +103,9 @@ Task UpdateProjectAsync(ProjectKey projectKey, RazorProjectInfo projectInfo, Can
125103
_logger.LogInformation($"Updating {projectKey.Id}.");
126104

127105
return _projectService
128-
.UpdateProjectAsync(
106+
.AddOrUpdateProjectAsync(
129107
projectKey,
108+
projectInfo.FilePath,
130109
projectInfo.Configuration,
131110
projectInfo.RootNamespace,
132111
projectInfo.DisplayName,
@@ -138,55 +117,25 @@ Task UpdateProjectAsync(ProjectKey projectKey, RazorProjectInfo projectInfo, Can
138117

139118
public void ProjectConfigurationFileChanged(ProjectConfigurationFileChangeEventArgs args)
140119
{
141-
var configurationFilePath = FilePathNormalizer.Normalize(args.ConfigurationFilePath);
142-
143120
switch (args.Kind)
144121
{
145122
case RazorFileChangeKind.Changed:
146123
{
147124
if (args.TryDeserialize(_options, out var projectInfo))
148125
{
149-
if (_filePathToProjectKeyMap.TryGetValue(configurationFilePath, out var projectKey))
150-
{
151-
_logger.LogInformation($"""
152-
Configuration file changed for project '{projectKey.Id}'.
153-
Configuration file path: '{configurationFilePath}'
154-
""");
155-
156-
_workQueue.AddWork(new UpdateProject(configurationFilePath, projectKey, projectInfo));
157-
}
158-
else
159-
{
160-
_logger.LogWarning($"""
161-
Adding project for previously unseen configuration file.
162-
Configuration file path: '{configurationFilePath}'
163-
""");
164-
165-
_workQueue.AddWork(new AddProject(configurationFilePath, projectInfo));
166-
}
126+
var projectKey = ProjectKey.From(projectInfo);
127+
_logger.LogInformation($"Configuration file changed for project '{projectKey.Id}'.");
128+
129+
_workQueue.AddWork(new UpdateProject(projectKey, projectInfo));
167130
}
168131
else
169132
{
170-
if (_filePathToProjectKeyMap.TryGetValue(configurationFilePath, out var projectKey))
171-
{
172-
_logger.LogWarning($"""
173-
Failed to deserialize after change to configuration file for project '{projectKey.Id}'.
174-
Configuration file path: '{configurationFilePath}'
175-
""");
176-
177-
// We found the last associated project file for the configuration file. Reset the project since we can't
178-
// accurately determine its configurations.
179-
180-
_workQueue.AddWork(new ResetProject(configurationFilePath, projectKey));
181-
}
182-
else
183-
{
184-
// Could not resolve an associated project file.
185-
_logger.LogWarning($"""
186-
Failed to deserialize after change to previously unseen configuration file.
187-
Configuration file path: '{configurationFilePath}'
188-
""");
189-
}
133+
var projectKey = args.GetProjectKey();
134+
_logger.LogWarning($"Failed to deserialize after change to configuration file for project '{projectKey.Id}'.");
135+
136+
// We found the last associated project file for the configuration file. Reset the project since we can't
137+
// accurately determine its configurations.
138+
_workQueue.AddWork(new ResetProject(projectKey));
190139
}
191140
}
192141

@@ -196,39 +145,28 @@ Failed to deserialize after change to previously unseen configuration file.
196145
{
197146
if (args.TryDeserialize(_options, out var projectInfo))
198147
{
199-
_workQueue.AddWork(new AddProject(configurationFilePath, projectInfo));
148+
var projectKey = ProjectKey.From(projectInfo);
149+
_logger.LogInformation($"Configuration file added for project '{projectKey.Id}'.");
150+
151+
// Update will add the project if it doesn't exist
152+
_workQueue.AddWork(new UpdateProject(projectKey, projectInfo));
200153
}
201154
else
202155
{
203156
// This is the first time we've seen this configuration file, but we can't deserialize it.
204157
// The only thing we can really do is issue a warning.
205-
_logger.LogWarning($"""
206-
Failed to deserialize previously unseen configuration file.
207-
Configuration file path: '{configurationFilePath}'
208-
""");
158+
_logger.LogWarning($"Failed to deserialize previously unseen configuration file '{args.ConfigurationFilePath}'");
209159
}
210160
}
211161

212162
break;
213163

214164
case RazorFileChangeKind.Removed:
215165
{
216-
if (ImmutableInterlocked.TryRemove(ref _filePathToProjectKeyMap, configurationFilePath, out var projectKey))
217-
{
218-
_logger.LogInformation($"""
219-
Configuration file removed for project '{projectKey}'.
220-
Configuration file path: '{configurationFilePath}'
221-
""");
166+
var projectKey = args.GetProjectKey();
167+
_logger.LogInformation($"Configuration file removed for project '{projectKey}'.");
222168

223-
_workQueue.AddWork(new ResetProject(configurationFilePath, projectKey));
224-
}
225-
else
226-
{
227-
_logger.LogWarning($"""
228-
Failed to resolve associated project on configuration removed event.
229-
Configuration file path: '{configurationFilePath}'
230-
""");
231-
}
169+
_workQueue.AddWork(new ResetProject(projectKey));
232170
}
233171

234172
break;

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/ProjectSystem/IRazorProjectService.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,14 @@ Task UpdateProjectAsync(
3636
ProjectWorkspaceState projectWorkspaceState,
3737
ImmutableArray<DocumentSnapshotHandle> documents,
3838
CancellationToken cancellationToken);
39+
40+
Task AddOrUpdateProjectAsync(
41+
ProjectKey projectKey,
42+
string filePath,
43+
RazorConfiguration? configuration,
44+
string? rootNamespace,
45+
string displayName,
46+
ProjectWorkspaceState projectWorkspaceState,
47+
ImmutableArray<DocumentSnapshotHandle> documents,
48+
CancellationToken cancellationToken);
3949
}

0 commit comments

Comments
 (0)