Skip to content

Commit

Permalink
Better handle methods in partial classes that don't have sequence points
Browse files Browse the repository at this point in the history
  • Loading branch information
davidwengier committed Sep 28, 2022
1 parent 773ee42 commit ad7e042
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,7 @@ protected static async Task GenerateFileAndVerifyAsync(

var masWorkspace = service.TryGetWorkspace();

var document = masWorkspace!.CurrentSolution.Projects.First().Documents.First();

Assert.Equal(document.FilePath, file.FilePath);
var document = masWorkspace!.CurrentSolution.Projects.First().Documents.First(d => d.FilePath == file.FilePath);

// Mapping the project from the generated document should map back to the original project
var provider = workspace.ExportProvider.GetExportedValues<IMetadataAsSourceFileProvider>().OfType<PdbSourceDocumentMetadataAsSourceFileProvider>().Single();
Expand Down Expand Up @@ -251,14 +249,19 @@ protected static void CompileTestSource(string path, SourceText source, Project
}

protected static void CompileTestSource(string dllFilePath, string sourceCodePath, string? pdbFilePath, string assemblyName, SourceText source, Project project, Location pdbLocation, Location sourceLocation, bool buildReferenceAssembly, bool windowsPdb, Encoding? fallbackEncoding = null)
{
CompileTestSource(dllFilePath, new[] { sourceCodePath }, pdbFilePath, assemblyName, new[] { source }, project, pdbLocation, sourceLocation, buildReferenceAssembly, windowsPdb, fallbackEncoding);
}

protected static void CompileTestSource(string dllFilePath, string[] sourceCodePaths, string? pdbFilePath, string assemblyName, SourceText[] sources, Project project, Location pdbLocation, Location sourceLocation, bool buildReferenceAssembly, bool windowsPdb, Encoding? fallbackEncoding = null)
{
var compilationFactory = project.Solution.Services.GetRequiredLanguageService<ICompilationFactoryService>(LanguageNames.CSharp);
var options = compilationFactory.GetDefaultCompilationOptions().WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var parseOptions = project.ParseOptions;

var compilation = compilationFactory
.CreateCompilation(assemblyName, options)
.AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(source, options: parseOptions, path: sourceCodePath))
.AddSyntaxTrees(sources.Select((s, i) => SyntaxFactory.ParseSyntaxTree(s, options: parseOptions, path: sourceCodePaths[i])))
.AddReferences(project.MetadataReferences);

IEnumerable<EmbeddedText>? embeddedTexts;
Expand All @@ -269,11 +272,14 @@ protected static void CompileTestSource(string dllFilePath, string sourceCodePat
else if (sourceLocation == Location.OnDisk)
{
embeddedTexts = null;
File.WriteAllText(sourceCodePath, source.ToString(), source.Encoding);
for (var i = 0; i < sources.Length; i++)
{
File.WriteAllText(sourceCodePaths[i], sources[i].ToString(), sources[i].Encoding);
}
}
else
{
embeddedTexts = new[] { EmbeddedText.FromSource(sourceCodePath, source) };
embeddedTexts = sources.Select((s, i) => EmbeddedText.FromSource(sourceCodePaths[i], s)).ToArray();
}

EmitOptions emitOptions;
Expand All @@ -300,7 +306,7 @@ protected static void CompileTestSource(string dllFilePath, string sourceCodePat

if (fallbackEncoding is null)
{
emitOptions = emitOptions.WithDefaultSourceFileEncoding(source.Encoding);
emitOptions = emitOptions.WithDefaultSourceFileEncoding(sources[0].Encoding);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,5 +882,61 @@ await RunTestAsync(async path =>
}
});
}

[Fact]
public async Task MethodInPartialType_NavigateToCorrectFile()
{
var source1 = @"
public partial class C
{
public void M1()
{
}
}
";
var source2 = @"
using System.Threading.Tasks;
public partial class C
{
public static async Task [|M2|]() => await M3();
private static async Task M3()
{
}
}
";

await RunTestAsync(async path =>
{
MarkupTestFile.GetSpan(source2, out source2, out var expectedSpan);
var sourceText1 = SourceText.From(source1, Encoding.UTF8);
var sourceText2 = SourceText.From(source2, Encoding.UTF8);
var workspace = TestWorkspace.Create(@$"
<Workspace>
<Project Language=""{LanguageNames.CSharp}"" CommonReferences=""true"" ReferencesOnDisk=""true"">
</Project>
</Workspace>", composition: GetTestComposition());
var project = workspace.CurrentSolution.Projects.First();
var dllFilePath = GetDllPath(path);
var sourceCodePath = GetSourceFilePath(path);
var pdbFilePath = GetPdbPath(path);
CompileTestSource(dllFilePath, new[] { Path.Combine(path, "source1.cs"), Path.Combine(path, "source2.cs") }, pdbFilePath, "reference", new[] { sourceText1, sourceText2 }, project, Location.Embedded, Location.Embedded, buildReferenceAssembly: false, windowsPdb: false);
project = project.AddMetadataReference(MetadataReference.CreateFromFile(GetDllPath(path)));
var mainCompilation = await project.GetRequiredCompilationAsync(CancellationToken.None).ConfigureAwait(false);
var symbol = mainCompilation.GetMember("C.M2");
AssertEx.NotNull(symbol, $"Couldn't find symbol to go-to-def for.");
await GenerateFileAndVerifyAsync(project, symbol, Location.Embedded, source2.ToString(), expectedSpan, expectNullResult: false);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
Expand All @@ -17,7 +16,6 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RemoveUnnecessaryImports;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Structure;
Expand Down Expand Up @@ -227,21 +225,26 @@ public PdbSourceDocumentMetadataAsSourceFileProvider(
navigateProject = metadataWorkspace.CurrentSolution.GetRequiredProject(projectId);
}

// If MetadataAsSourceHelpers.GetLocationInGeneratedSourceAsync can't find the actual document to navigate to, it will fall back
// to the document passed in, which we just use the first document for.
// TODO: Support results from multiple source files: https://github.com/dotnet/roslyn/issues/55834
var firstSourceFileInfo = sourceFileInfos[0]!;
var documentPath = firstSourceFileInfo.FilePath;
var document = navigateProject.Documents.FirstOrDefault(d => d.FilePath?.Equals(documentPath, StringComparison.OrdinalIgnoreCase) ?? false);
var firstDocumentFilePath = sourceFileInfos[0]!.FilePath;
var firstDocument = navigateProject.Documents.FirstOrDefault(d => d.FilePath?.Equals(firstDocumentFilePath, StringComparison.OrdinalIgnoreCase) ?? false);
var navigateLocation = await MetadataAsSourceHelpers.GetLocationInGeneratedSourceAsync(symbolId, firstDocument, cancellationToken).ConfigureAwait(false);

var navigateLocation = await MetadataAsSourceHelpers.GetLocationInGeneratedSourceAsync(symbolId, document, cancellationToken).ConfigureAwait(false);
// In the case of partial classes, finding the location in the generated source may return a location in a different document, so we
// have to make sure to look it up again.
var navigateDocument = navigateProject.GetDocument(navigateLocation.SourceTree);
Contract.ThrowIfNull(navigateDocument);
var sourceDescription = sourceFileInfos.FirstOrDefault(sfi => sfi!.FilePath?.Equals(navigateDocument.FilePath, StringComparison.OrdinalIgnoreCase) ?? false)?.SourceDescription ?? FeaturesResources.from_metadata;

var documentName = string.Format(
"{0} [{1}]",
navigateDocument!.Name,
firstSourceFileInfo.SourceDescription);
var documentTooltip = sourceDocuments[0].FilePath + Environment.NewLine + dllPath;
navigateDocument.Name,
sourceDescription);
var documentTooltip = navigateDocument.FilePath + Environment.NewLine + dllPath;

return new MetadataAsSourceFile(documentPath, navigateLocation, documentName, documentTooltip);
return new MetadataAsSourceFile(navigateDocument.FilePath, navigateLocation, documentName, documentTooltip);
}

private ProjectInfo? CreateProjectInfo(Workspace workspace, Project project, ImmutableDictionary<string, string> pdbCompilationOptions, string assemblyName, string assemblyVersion)
Expand Down

0 comments on commit ad7e042

Please sign in to comment.