Skip to content

Commit f7e10cf

Browse files
Ensure Location.None in additional locations can round trip correctly (#76623)
If an analyzer adds `Location.None` in additional location, it gets removed away in the code fix. This can be useful to preserve in case the analyzer can have multiple additional locations, some of which are kinda "optional", but each has a separate meaning that is determined by its index. Currently, the only way around that is to not use Location.None, and instead use the properties map to determine the meanings. **NOTE:** Even after this is merged, analyzer/codefix authors shouldn't really rely on the new behavior if they intend to support earlier versions of VS. This PR is intended to stabilize things in future when the fix becomes in an "old enough" VS version. FYI @Sergio0694
2 parents 978be80 + a1c72a3 commit f7e10cf

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

src/EditorFeatures/Test/Diagnostics/DiagnosticDataTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,43 @@ public async Task DiagnosticData_ExternalAdditionalLocationIsPreserved()
167167
Assert.Equal(externalAdditionalLocation.UnmappedFileSpan, roundTripAdditionalLocation.UnmappedFileSpan);
168168
}
169169

170+
[Fact]
171+
public async Task DiagnosticData_NoneAdditionalLocationIsPreserved()
172+
{
173+
using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures);
174+
175+
var additionalDocument = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp)
176+
.AddDocument("test.cs", "", filePath: "test.cs");
177+
178+
var document = additionalDocument.Project.Documents.Single();
179+
180+
var noneAdditionalLocation = new DiagnosticDataLocation(new FileLinePositionSpan("", default));
181+
182+
var diagnosticData = new DiagnosticData(
183+
id: "test1",
184+
category: "Test",
185+
message: "test1 message",
186+
severity: DiagnosticSeverity.Info,
187+
defaultSeverity: DiagnosticSeverity.Info,
188+
isEnabledByDefault: true,
189+
warningLevel: 1,
190+
projectId: document.Project.Id,
191+
customTags: [],
192+
properties: ImmutableDictionary<string, string>.Empty,
193+
location: new DiagnosticDataLocation(new FileLinePositionSpan(document.FilePath, span: default), document.Id),
194+
additionalLocations: [noneAdditionalLocation],
195+
language: document.Project.Language);
196+
197+
var diagnostic = await diagnosticData.ToDiagnosticAsync(document.Project, CancellationToken.None);
198+
var roundTripDiagnosticData = DiagnosticData.Create(diagnostic, document);
199+
200+
var roundTripAdditionalLocation = Assert.Single(roundTripDiagnosticData.AdditionalLocations);
201+
Assert.Null(noneAdditionalLocation.DocumentId);
202+
Assert.Null(roundTripAdditionalLocation.DocumentId);
203+
Assert.Equal(noneAdditionalLocation.UnmappedFileSpan, roundTripAdditionalLocation.UnmappedFileSpan);
204+
Assert.Same(diagnostic.AdditionalLocations.Single(), Location.None);
205+
}
206+
170207
[Fact]
171208
public async Task DiagnosticData_SourceGeneratedDocumentLocationIsPreserved()
172209
{

src/Workspaces/Core/Portable/Diagnostics/DiagnosticData.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,24 @@ private static ImmutableArray<DiagnosticDataLocation> GetAdditionalLocations(Tex
263263
{
264264
if (location.IsInSource)
265265
{
266-
builder.AddIfNotNull(CreateLocation(document.Project.Solution.GetDocument(location.SourceTree), location));
266+
builder.Add(CreateLocation(document.Project.Solution.GetDocument(location.SourceTree), location));
267267
}
268268
else if (location.Kind == LocationKind.ExternalFile)
269269
{
270270
var textDocumentId = document.Project.GetDocumentForExternalLocation(location);
271-
builder.AddIfNotNull(CreateLocation(document.Project.GetTextDocument(textDocumentId), location));
271+
builder.Add(CreateLocation(document.Project.GetTextDocument(textDocumentId), location));
272272
}
273+
else if (location.Kind == LocationKind.None)
274+
{
275+
builder.Add(CreateLocation(document: null, location));
276+
}
277+
// TODO: Should we throw an exception in an else?
278+
// This will be reachable if a user creates his own type inheriting Location, and
279+
// returns, e.g, LocationKind.XmlFile in Kind override.
280+
// The case for custom `Location`s in general will be hard (if possible at all) to
281+
// always round trip correctly.
282+
// Or, maybe just always create a location with null document, so at least we guarantee that
283+
// the count of additional location created by analyzer always matches what end up being in the code fix.
273284
}
274285

275286
return builder.ToImmutableAndClear();

0 commit comments

Comments
 (0)