diff --git a/src/EditorFeatures/Core/LanguageServer/EditorHoverCreationService.cs b/src/EditorFeatures/Core/LanguageServer/EditorHoverCreationService.cs index f679aa667e98e..424d7a2b75ea4 100644 --- a/src/EditorFeatures/Core/LanguageServer/EditorHoverCreationService.cs +++ b/src/EditorFeatures/Core/LanguageServer/EditorHoverCreationService.cs @@ -12,9 +12,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.QuickInfo; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer { @@ -31,12 +29,15 @@ public EditorLspHoverResultCreationService(IGlobalOptionService optionService) } public async Task CreateHoverAsync( - SourceText text, string language, QuickInfoItem info, Document? document, ClientCapabilities? clientCapabilities, CancellationToken cancellationToken) + Document document, QuickInfoItem info, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) { var supportsVSExtensions = clientCapabilities.HasVisualStudioLspCapability(); if (!supportsVSExtensions) - return DefaultLspHoverResultCreationService.CreateDefaultHover(text, language, info, clientCapabilities); + return await DefaultLspHoverResultCreationService.CreateDefaultHoverAsync(document, info, clientCapabilities, cancellationToken).ConfigureAwait(false); + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var language = document.Project.Language; var classificationOptions = _optionService.GetClassificationOptions(language); diff --git a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs index 884334c2b2a67..7d0547a108f51 100644 --- a/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs +++ b/src/Features/Core/Portable/Structure/BlockStructureServiceWithProviders.cs @@ -55,15 +55,6 @@ public override async Task GetBlockStructureAsync( return GetBlockStructure(context, _providers); } - public BlockStructure GetBlockStructure( - SyntaxTree syntaxTree, - in BlockStructureOptions options, - CancellationToken cancellationToken) - { - var context = CreateContext(syntaxTree, options, cancellationToken); - return GetBlockStructure(context, _providers); - } - private static BlockStructureContext CreateContext( SyntaxTree syntaxTree, in BlockStructureOptions options, diff --git a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs index 6c8a52aabff07..ae49a36b9c07b 100644 --- a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Structure; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; @@ -40,37 +41,24 @@ public FoldingRangesHandler(IGlobalOptionService globalOptions) if (document is null) return null; - var blockStructureService = document.Project.Services.GetService(); - if (blockStructureService == null) - { - return Array.Empty(); - } - var options = _globalOptions.GetBlockStructureOptions(document.Project); - var blockStructure = await blockStructureService.GetBlockStructureAsync(document, options, cancellationToken).ConfigureAwait(false); - if (blockStructure == null) - { - return Array.Empty(); - } - - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - return GetFoldingRanges(blockStructure, text); + return await GetFoldingRangesAsync(document, options, cancellationToken).ConfigureAwait(false); } - public static FoldingRange[] GetFoldingRanges( - SyntaxTree syntaxTree, - LanguageServices languageServices, - in BlockStructureOptions options, + /// + /// Used here and by lsif generator. + /// + public static async Task GetFoldingRangesAsync( + Document document, + BlockStructureOptions options, CancellationToken cancellationToken) { - var blockStructureService = (BlockStructureServiceWithProviders)languageServices.GetRequiredService(); - var blockStructure = blockStructureService.GetBlockStructure(syntaxTree, options, cancellationToken); + var blockStructureService = document.GetRequiredLanguageService(); + var blockStructure = await blockStructureService.GetBlockStructureAsync(document, options, cancellationToken).ConfigureAwait(false); if (blockStructure == null) - { return Array.Empty(); - } - var text = syntaxTree.GetText(cancellationToken); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); return GetFoldingRanges(blockStructure, text); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs index 96acd1253bf72..72e0ff3d8044a 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs @@ -4,7 +4,6 @@ using System; using System.Composition; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -12,7 +11,7 @@ using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.QuickInfo; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler @@ -46,52 +45,26 @@ public HoverHandler(IGlobalOptionService globalOptions) var clientCapabilities = context.GetRequiredClientCapabilities(); var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); - var quickInfoService = document.Project.Services.GetRequiredService(); var options = _globalOptions.GetSymbolDescriptionOptions(document.Project.Language); - var info = await quickInfoService.GetQuickInfoAsync(document, position, options, cancellationToken).ConfigureAwait(false); - if (info == null) - return null; - - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - return await GetHoverAsync( - document.Project.Solution.Services, info, text, document.Project.Language, - document, clientCapabilities, cancellationToken).ConfigureAwait(false); + return await GetHoverAsync(document, position, options, clientCapabilities, cancellationToken).ConfigureAwait(false); } internal static async Task GetHoverAsync( - SemanticModel semanticModel, + Document document, int position, SymbolDescriptionOptions options, - LanguageServices languageServices, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) { - Debug.Assert(semanticModel.Language is LanguageNames.CSharp or LanguageNames.VisualBasic); - // Get the quick info service to compute quick info. // This code path is only invoked for C# and VB, so we can directly cast to QuickInfoServiceWithProviders. - var quickInfoService = (QuickInfoServiceWithProviders)languageServices.GetRequiredService(); - var info = await quickInfoService.GetQuickInfoAsync(semanticModel, position, options, cancellationToken).ConfigureAwait(false); + var quickInfoService = document.GetRequiredLanguageService(); + var info = await quickInfoService.GetQuickInfoAsync(document, position, options, cancellationToken).ConfigureAwait(false); if (info == null) return null; - var text = await semanticModel.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); - return await GetHoverAsync( - languageServices.SolutionServices, info, text, semanticModel.Language, document: null, clientCapabilities, cancellationToken).ConfigureAwait(false); - } - - private static async Task GetHoverAsync( - SolutionServices solutionServices, - QuickInfoItem info, - SourceText text, - string language, - Document? document, - ClientCapabilities? clientCapabilities, - CancellationToken cancellationToken) - { - var hoverService = solutionServices.GetRequiredService(); - return await hoverService.CreateHoverAsync( - text, language, info, document, clientCapabilities, cancellationToken).ConfigureAwait(false); + var hoverService = document.Project.Solution.Services.GetRequiredService(); + return await hoverService.CreateHoverAsync(document, info, clientCapabilities, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Hover/ILspHoverResultCreationService.cs b/src/Features/LanguageServer/Protocol/Handler/Hover/ILspHoverResultCreationService.cs index 9001aac425101..30f513b083506 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Hover/ILspHoverResultCreationService.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Hover/ILspHoverResultCreationService.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.QuickInfo; -using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.Handler @@ -19,7 +18,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler internal interface ILspHoverResultCreationService : IWorkspaceService { Task CreateHoverAsync( - SourceText text, string language, QuickInfoItem info, Document? document, ClientCapabilities? clientCapabilities, CancellationToken cancellationToken); + Document document, QuickInfoItem info, ClientCapabilities clientCapabilities, CancellationToken cancellationToken); } [ExportWorkspaceService(typeof(ILspHoverResultCreationService)), Shared] @@ -31,10 +30,10 @@ public DefaultLspHoverResultCreationService() { } - public Task CreateHoverAsync(SourceText text, string language, QuickInfoItem info, Document? document, ClientCapabilities? clientCapabilities, CancellationToken cancellationToken) - => Task.FromResult(CreateDefaultHover(text, language, info, clientCapabilities)); + public Task CreateHoverAsync(Document document, QuickInfoItem info, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) + => CreateDefaultHoverAsync(document, info, clientCapabilities, cancellationToken); - public static Hover CreateDefaultHover(SourceText text, string language, QuickInfoItem info, ClientCapabilities? clientCapabilities) + public static async Task CreateDefaultHoverAsync(Document document, QuickInfoItem info, ClientCapabilities clientCapabilities, CancellationToken cancellationToken) { var clientSupportsMarkdown = clientCapabilities?.TextDocument?.Hover?.ContentFormat.Contains(MarkupKind.Markdown) == true; @@ -43,6 +42,9 @@ public static Hover CreateDefaultHover(SourceText text, string language, QuickIn .SelectMany(section => section.TaggedParts.Add(new TaggedText(TextTags.LineBreak, Environment.NewLine))) .ToImmutableArray(); + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var language = document.Project.Language; + return new Hover { Range = ProtocolConversions.TextSpanToRange(info.Span, text), diff --git a/src/Features/Lsif/Generator/CompilerInvocation.cs b/src/Features/Lsif/Generator/CompilerInvocation.cs index 0ad226660d5c8..38d8653728670 100644 --- a/src/Features/Lsif/Generator/CompilerInvocation.cs +++ b/src/Features/Lsif/Generator/CompilerInvocation.cs @@ -18,29 +18,16 @@ namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator { - internal class CompilerInvocation + internal static class CompilerInvocation { - public Compilation Compilation { get; } - public LanguageServices LanguageServices { get; } - public string ProjectFilePath { get; } - public GeneratorOptions Options { get; } - - public CompilerInvocation(Compilation compilation, LanguageServices languageServices, string projectFilePath, GeneratorOptions options) - { - Compilation = compilation; - LanguageServices = languageServices; - ProjectFilePath = projectFilePath; - Options = options; - } - - public static async Task CreateFromJsonAsync(string jsonContents) + public static async Task CreateFromJsonAsync(string jsonContents) { var invocationInfo = JsonConvert.DeserializeObject(jsonContents); Assumes.Present(invocationInfo); return await CreateFromInvocationInfoAsync(invocationInfo); } - public static async Task CreateFromInvocationInfoAsync(CompilerInvocationInfo invocationInfo) + public static async Task CreateFromInvocationInfoAsync(CompilerInvocationInfo invocationInfo) { // We will use a Workspace to simplify the creation of the compilation, but will be careful not to return the Workspace instance from this class. // We will still provide the language services which are used by the generator itself, but we don't tie it to a Workspace object so we can @@ -113,10 +100,7 @@ public static async Task CreateFromInvocationInfoAsync(Compi hostObjectType: null); var solution = workspace.CurrentSolution.AddProject(projectInfo); - var compilation = await solution.GetRequiredProject(projectId).GetRequiredCompilationAsync(CancellationToken.None); - var options = GeneratorOptions.Default; - - return new CompilerInvocation(compilation, languageServices, invocationInfo.ProjectFilePath, options); + return solution.GetRequiredProject(projectId); // Local methods: DocumentInfo CreateDocumentInfo(string unmappedPath) diff --git a/src/Features/Lsif/Generator/Generator.cs b/src/Features/Lsif/Generator/Generator.cs index 6b0baad0e2ac4..4053e77e18316 100644 --- a/src/Features/Lsif/Generator/Generator.cs +++ b/src/Features/Lsif/Generator/Generator.cs @@ -71,8 +71,12 @@ public static Generator CreateAndWriteCapabilitiesVertex(ILsifJsonWriter lsifJso return generator; } - public async Task GenerateForCompilationAsync(Compilation compilation, string projectPath, LanguageServices languageServices, GeneratorOptions options) + public async Task GenerateForProjectAsync(Project project, GeneratorOptions options) { + var compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); + var projectPath = project.FilePath; + Contract.ThrowIfNull(projectPath); + var projectVertex = new Graph.LsifProject( kind: GetLanguageKind(compilation.Language), new Uri(projectPath), @@ -104,12 +108,10 @@ public async Task GenerateForCompilationAsync(Compilation compilation, string pr }; var tasks = new List(); - foreach (var syntaxTree in compilation.SyntaxTrees) + foreach (var document in await project.GetAllRegularAndSourceGeneratedDocumentsAsync().ConfigureAwait(false)) { tasks.Add(Task.Run(async () => { - var semanticModel = compilation.GetSemanticModel(syntaxTree); - // We generate the document contents into an in-memory copy, and then write that out at once at the end. This // allows us to collect everything and avoid a lot of fine-grained contention on the write to the single // LSIF file. Because of the rule that vertices must be written before they're used by an edge, we'll flush any top- @@ -117,7 +119,7 @@ public async Task GenerateForCompilationAsync(Compilation compilation, string pr // are allowed and might flush other unrelated stuff at the same time, but there's no harm -- the "causality" ordering // is preserved. var documentWriter = new BatchingLsifJsonWriter(_lsifJsonWriter); - var documentId = await GenerateForDocumentAsync(semanticModel, languageServices, options, topLevelSymbolsResultSetTracker, documentWriter, _idFactory); + var documentId = await GenerateForDocumentAsync(document, options, topLevelSymbolsResultSetTracker, documentWriter, _idFactory); topLevelSymbolsWriter.FlushToUnderlyingAndEmpty(); documentWriter.FlushToUnderlyingAndEmpty(); @@ -144,42 +146,67 @@ public async Task GenerateForCompilationAsync(Compilation compilation, string pr /// leak outside a file. /// private static async Task> GenerateForDocumentAsync( - SemanticModel semanticModel, - LanguageServices languageServices, + Document document, GeneratorOptions options, IResultSetTracker topLevelSymbolsResultSetTracker, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) { - var syntaxTree = semanticModel.SyntaxTree; - var sourceText = semanticModel.SyntaxTree.GetText(); - var syntaxFactsService = languageServices.GetRequiredService(); - var semanticFactsService = languageServices.GetRequiredService(); + // Create and keep the semantic model alive for this document. That way all work/services we kick off that + // use this document can benefit from that single shared model. + var semanticModel = await document.GetRequiredSemanticModelAsync(CancellationToken.None); - string? contentBase64Encoded = null; + var (uri, contentBase64Encoded) = await GetUriAndContentAsync(document).ConfigureAwait(false); - var uri = syntaxTree.FilePath; + var documentVertex = new Graph.LsifDocument(new Uri(uri, UriKind.RelativeOrAbsolute), GetLanguageKind(semanticModel.Language), contentBase64Encoded, idFactory); + lsifJsonWriter.Write(documentVertex); + lsifJsonWriter.Write(new Event(Event.EventKind.Begin, documentVertex.GetId(), idFactory)); - // TODO: move to checking the enum member mentioned in https://github.com/dotnet/roslyn/issues/49326 when that - // is implemented. In the mean time, we'll use a heuristic of the path being a relative path as a way to indicate - // this is a source generated file. - if (!PathUtilities.IsAbsolute(syntaxTree.FilePath)) - { - var text = semanticModel.SyntaxTree.GetText(); + // We will walk the file token-by-token, making a range for each one and then attaching information for it + var rangeVertices = new List>(); + await GenerateDocumentRangesAndLinks(document, documentVertex, options, topLevelSymbolsResultSetTracker, lsifJsonWriter, idFactory, rangeVertices).ConfigureAwait(false); + lsifJsonWriter.Write(Edge.Create("contains", documentVertex.GetId(), rangeVertices, idFactory)); - // We always use UTF-8 encoding when writing out file contents, as that's expected by LSIF implementations. - // TODO: when we move to .NET Core, is there a way to reduce allocations here? - contentBase64Encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(text.ToString())); + await GenerateDocumentFoldingRangesAsync(document, documentVertex, options, lsifJsonWriter, idFactory).ConfigureAwait(false); - // There is a triple slash here, so the "host" portion of the URI is empty, similar to - // how file URIs work. - uri = "source-generated:///" + syntaxTree.FilePath.Replace('\\', '/'); - } + lsifJsonWriter.Write(new Event(Event.EventKind.End, documentVertex.GetId(), idFactory)); - var documentVertex = new Graph.LsifDocument(new Uri(uri, UriKind.RelativeOrAbsolute), GetLanguageKind(semanticModel.Language), contentBase64Encoded, idFactory); + GC.KeepAlive(semanticModel); - lsifJsonWriter.Write(documentVertex); - lsifJsonWriter.Write(new Event(Event.EventKind.Begin, documentVertex.GetId(), idFactory)); + return documentVertex.GetId(); + } + + private static async Task GenerateDocumentFoldingRangesAsync( + Document document, + LsifDocument documentVertex, + GeneratorOptions options, + ILsifJsonWriter lsifJsonWriter, + IdFactory idFactory) + { + var foldingRanges = await FoldingRangesHandler.GetFoldingRangesAsync( + document, options.BlockStructureOptions, CancellationToken.None).ConfigureAwait(false); + var foldingRangeResult = new FoldingRangeResult(foldingRanges, idFactory); + lsifJsonWriter.Write(foldingRangeResult); + lsifJsonWriter.Write(Edge.Create(Methods.TextDocumentFoldingRangeName, documentVertex.GetId(), foldingRangeResult.GetId(), idFactory)); + } + + private static async Task GenerateDocumentRangesAndLinks( + Document document, + LsifDocument documentVertex, + GeneratorOptions options, + IResultSetTracker topLevelSymbolsResultSetTracker, + ILsifJsonWriter lsifJsonWriter, + IdFactory idFactory, + List> rangeVertices) + { + var languageServices = document.Project.Services; + + var semanticModel = await document.GetRequiredSemanticModelAsync(CancellationToken.None); + + var syntaxTree = semanticModel.SyntaxTree; + var sourceText = semanticModel.SyntaxTree.GetText(); + var syntaxFactsService = languageServices.GetRequiredService(); + var semanticFactsService = languageServices.GetRequiredService(); // As we are processing this file, we are going to encounter symbols that have a shared resultSet with other documents like types // or methods. We're also going to encounter locals that never leave this document. We don't want those locals being held by @@ -206,9 +233,6 @@ SymbolKind.RangeVariable or } }); - // We will walk the file token-by-token, making a range for each one and then attaching information for it - var rangeVertices = new List>(); - foreach (var syntaxToken in syntaxTree.GetRoot().DescendantTokens(descendIntoTrivia: true)) { // We'll only create the Range vertex once it's needed, but any number of bits of code might create it first, @@ -312,7 +336,7 @@ void MarkImplementationOfSymbol(ISymbol baseMember) if (symbolResultsTracker.ResultSetNeedsInformationalEdgeAdded(symbolForLinkedResultSet, Methods.TextDocumentHoverName)) { var hover = await HoverHandler.GetHoverAsync( - semanticModel, syntaxToken.SpanStart, options.SymbolDescriptionOptions, languageServices, LspClientCapabilities, CancellationToken.None); + document, syntaxToken.SpanStart, options.SymbolDescriptionOptions, LspClientCapabilities, CancellationToken.None); if (hover != null) { var hoverResult = new HoverResult(hover, idFactory); @@ -322,17 +346,27 @@ void MarkImplementationOfSymbol(ISymbol baseMember) } } } + } - lsifJsonWriter.Write(Edge.Create("contains", documentVertex.GetId(), rangeVertices, idFactory)); + private static async Task<(string uri, string? contentBase64Encoded)> GetUriAndContentAsync(Document document) + { + string? contentBase64Encoded = null; + var uri = document.FilePath ?? ""; - // Write the folding ranges for the document. - var foldingRanges = FoldingRangesHandler.GetFoldingRanges(syntaxTree, languageServices, options.BlockStructureOptions, CancellationToken.None); - var foldingRangeResult = new FoldingRangeResult(foldingRanges, idFactory); - lsifJsonWriter.Write(foldingRangeResult); - lsifJsonWriter.Write(Edge.Create(Methods.TextDocumentFoldingRangeName, documentVertex.GetId(), foldingRangeResult.GetId(), idFactory)); + if (document is SourceGeneratedDocument) + { + var text = await document.GetTextAsync().ConfigureAwait(false); - lsifJsonWriter.Write(new Event(Event.EventKind.End, documentVertex.GetId(), idFactory)); - return documentVertex.GetId(); + // We always use UTF-8 encoding when writing out file contents, as that's expected by LSIF implementations. + // TODO: when we move to .NET Core, is there a way to reduce allocations here? + contentBase64Encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(text.ToString())); + + // There is a triple slash here, so the "host" portion of the URI is empty, similar to + // how file URIs work. + uri = "source-generated:///" + uri.Replace('\\', '/'); + } + + return (uri, contentBase64Encoded); } private static bool IncludeSymbolInReferences(ISymbol symbol) diff --git a/src/Features/Lsif/Generator/Program.cs b/src/Features/Lsif/Generator/Program.cs index 49759ff226a4c..b22e3596f1456 100644 --- a/src/Features/Lsif/Generator/Program.cs +++ b/src/Features/Lsif/Generator/Program.cs @@ -12,11 +12,13 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Build.Locator; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.Writing; using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.CodeAnalysis.Shared.Extensions; using CompilerInvocationsReader = Microsoft.Build.Logging.StructuredLogger.CompilerInvocationsReader; namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator @@ -172,12 +174,12 @@ private static async Task GenerateWithMSBuildWorkspaceAsync( if (project.SupportsCompilation && project.FilePath != null) { var compilationCreationStopwatch = Stopwatch.StartNew(); - var compilation = (await project.GetCompilationAsync())!; + var compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); await logFile.WriteLineAsync($"Fetch of compilation for {project.FilePath} completed in {compilationCreationStopwatch.Elapsed.ToDisplayString()}."); var generationForProjectStopwatch = Stopwatch.StartNew(); - await lsifGenerator.GenerateForCompilationAsync(compilation, project.FilePath, project.Services, options); + await lsifGenerator.GenerateForProjectAsync(project, options); generationForProjectStopwatch.Stop(); totalTimeInGenerationPhase += generationForProjectStopwatch.Elapsed; @@ -195,14 +197,14 @@ private static async Task GenerateFromCompilerInvocationAsync(FileInfo compilerI await logFile.WriteLineAsync($"Processing compiler invocation from {compilerInvocationFile.FullName}..."); var compilerInvocationLoadStopwatch = Stopwatch.StartNew(); - var compilerInvocation = await CompilerInvocation.CreateFromJsonAsync(File.ReadAllText(compilerInvocationFile.FullName)); + var project = await CompilerInvocation.CreateFromJsonAsync(File.ReadAllText(compilerInvocationFile.FullName)); await logFile.WriteLineAsync($"Load of the project completed in {compilerInvocationLoadStopwatch.Elapsed.ToDisplayString()}."); var generationStopwatch = Stopwatch.StartNew(); var lsifGenerator = Generator.CreateAndWriteCapabilitiesVertex(lsifWriter); - await lsifGenerator.GenerateForCompilationAsync(compilerInvocation.Compilation, compilerInvocation.ProjectFilePath, compilerInvocation.LanguageServices, compilerInvocation.Options); - await logFile.WriteLineAsync($"Generation for {compilerInvocation.ProjectFilePath} completed in {generationStopwatch.Elapsed.ToDisplayString()}."); + await lsifGenerator.GenerateForProjectAsync(project, GeneratorOptions.Default); + await logFile.WriteLineAsync($"Generation for {project.FilePath} completed in {generationStopwatch.Elapsed.ToDisplayString()}."); } // This method can't be loaded until we've registered MSBuild with MSBuildLocator, as otherwise we might load a type prematurely. @@ -226,11 +228,11 @@ private static async Task GenerateFromBinaryLogAsync(FileInfo binLog, ILsifJsonW Tool = msbuildInvocation.Language == Microsoft.Build.Logging.StructuredLogger.CompilerInvocation.CSharp ? "csc" : "vbc" }; - var compilerInvocation = await CompilerInvocation.CreateFromInvocationInfoAsync(invocationInfo); + var project = await CompilerInvocation.CreateFromInvocationInfoAsync(invocationInfo); var generationStopwatch = Stopwatch.StartNew(); - await lsifGenerator.GenerateForCompilationAsync(compilerInvocation.Compilation, compilerInvocation.ProjectFilePath, compilerInvocation.LanguageServices, compilerInvocation.Options); - await logFile.WriteLineAsync($"Generation for {compilerInvocation.ProjectFilePath} completed in {generationStopwatch.Elapsed.ToDisplayString()}."); + await lsifGenerator.GenerateForProjectAsync(project, GeneratorOptions.Default); + await logFile.WriteLineAsync($"Generation for {project.FilePath} completed in {generationStopwatch.Elapsed.ToDisplayString()}."); } } } diff --git a/src/Features/Lsif/GeneratorTest/CompilerInvocationTests.vb b/src/Features/Lsif/GeneratorTest/CompilerInvocationTests.vb index 64246e04f7061..2d5af6872c5e6 100644 --- a/src/Features/Lsif/GeneratorTest/CompilerInvocationTests.vb +++ b/src/Features/Lsif/GeneratorTest/CompilerInvocationTests.vb @@ -12,7 +12,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ' PortableExecutableReference.CreateFromFile implicitly reads the file so the file must exist. Dim referencePath = GetType(Object).Assembly.Location - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""csc"", ""arguments"": ""/noconfig /nowarn:1701,1702 /fullpaths /define:DEBUG /reference:" + referencePath.Replace("\", "\\") + " Z:\\SourceFile.cs /target:library /out:Z:\\Output.dll"", @@ -20,15 +20,16 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ""sourceRootPath"": ""Z:\\"" }") - Assert.Equal(LanguageNames.CSharp, compilerInvocation.Compilation.Language) - Assert.Equal("Z:\Project.csproj", compilerInvocation.ProjectFilePath) - Assert.Equal(OutputKind.DynamicallyLinkedLibrary, compilerInvocation.Compilation.Options.OutputKind) + Assert.Equal(LanguageNames.CSharp, project.Language) + Assert.Equal("Z:\Project.csproj", project.FilePath) + Dim compilation = Await project.GetCompilationAsync() + Assert.Equal(OutputKind.DynamicallyLinkedLibrary, compilation.Options.OutputKind) - Dim syntaxTree = Assert.Single(compilerInvocation.Compilation.SyntaxTrees) + Dim syntaxTree = Assert.Single(compilation.SyntaxTrees) Assert.Equal("Z:\SourceFile.cs", syntaxTree.FilePath) Assert.Equal("DEBUG", Assert.Single(syntaxTree.Options.PreprocessorSymbolNames)) - Dim metadataReference = Assert.Single(compilerInvocation.Compilation.References) + Dim metadataReference = Assert.Single(compilation.References) Assert.Equal(referencePath, DirectCast(metadataReference, PortableExecutableReference).FilePath) End Function @@ -38,7 +39,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ' PortableExecutableReference.CreateFromFile implicitly reads the file so the file must exist. Dim referencePath = GetType(Object).Assembly.Location - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""vbc"", ""arguments"": ""/noconfig /nowarn:1701,1702 /fullpaths /define:DEBUG /reference:" + referencePath.Replace("\", "\\") + " Z:\\SourceFile.vb /target:library /out:Z:\\Output.dll"", @@ -46,15 +47,16 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ""sourceRootPath"": ""Z:\\"" }") - Assert.Equal(LanguageNames.VisualBasic, compilerInvocation.Compilation.Language) - Assert.Equal("Z:\Project.vbproj", compilerInvocation.ProjectFilePath) - Assert.Equal(OutputKind.DynamicallyLinkedLibrary, compilerInvocation.Compilation.Options.OutputKind) + Assert.Equal(LanguageNames.VisualBasic, project.Language) + Assert.Equal("Z:\Project.vbproj", project.FilePath) + Dim compilation = Await project.GetCompilationAsync() + Assert.Equal(OutputKind.DynamicallyLinkedLibrary, compilation.Options.OutputKind) - Dim syntaxTree = Assert.Single(compilerInvocation.Compilation.SyntaxTrees) + Dim syntaxTree = Assert.Single(compilation.SyntaxTrees) Assert.Equal("Z:\SourceFile.vb", syntaxTree.FilePath) Assert.Contains("DEBUG", syntaxTree.Options.PreprocessorSymbolNames) - Dim metadataReference = Assert.Single(compilerInvocation.Compilation.References) + Dim metadataReference = Assert.Single(compilation.References) Assert.Equal(referencePath, DirectCast(metadataReference, PortableExecutableReference).FilePath) End Function @@ -62,7 +64,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests Public Async Function TestSourceFilePathMappingWithDriveLetters( from As String, [to] As String) As Task - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""csc"", ""arguments"": ""/noconfig /nowarn:1701,1702 /fullpaths /define:DEBUG F:\\SourceFile.cs /target:library /out:F:\\Output.dll"", @@ -75,14 +77,15 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests }] }") - Dim syntaxTree = Assert.Single(compilerInvocation.Compilation.SyntaxTrees) + Dim compilation = Await project.GetCompilationAsync() + Dim syntaxTree = Assert.Single(compilation.SyntaxTrees) Assert.Equal("T:\SourceFile.cs", syntaxTree.FilePath) End Function Public Async Function TestSourceFilePathMappingWithSubdirectoriesWithoutTrailingSlashes() As Task - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""csc"", ""arguments"": ""/noconfig /nowarn:1701,1702 /fullpaths /define:DEBUG F:\\Directory\\SourceFile.cs /target:library /out:F:\\Output.dll"", @@ -95,14 +98,15 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests }] }") - Dim syntaxTree = Assert.Single(compilerInvocation.Compilation.SyntaxTrees) + Dim compilation = Await project.GetCompilationAsync() + Dim syntaxTree = Assert.Single(compilation.SyntaxTrees) Assert.Equal("T:\Directory\SourceFile.cs", syntaxTree.FilePath) End Function Public Async Function TestSourceFilePathMappingWithSubdirectoriesWithDoubleSlashesInFilePath() As Task - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""csc"", ""arguments"": ""/noconfig /nowarn:1701,1702 /fullpaths /define:DEBUG F:\\Directory\\\\SourceFile.cs /target:library /out:F:\\Output.dll"", @@ -115,7 +119,8 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests }] }") - Dim syntaxTree = Assert.Single(compilerInvocation.Compilation.SyntaxTrees) + Dim compilation = Await Project.GetCompilationAsync() + Dim syntaxTree = Assert.Single(compilation.SyntaxTrees) Assert.Equal("T:\Directory\SourceFile.cs", syntaxTree.FilePath) End Function @@ -133,7 +138,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ruleSet.WriteAllText(RuleSetContents) ' We will test that if we redirect the ruleset to the temporary file that we wrote that the values are still read. - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""csc"", ""arguments"": ""/noconfig /nowarn:1701,1702 /fullpaths /define:DEBUG /ruleset:F:\\Ruleset.ruleset /out:Output.dll"", @@ -146,7 +151,8 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests }] }") - Assert.Equal(ReportDiagnostic.Warn, compilerInvocation.Compilation.Options.SpecificDiagnosticOptions("CA1001")) + Dim compilation = Await project.GetCompilationAsync() + Assert.Equal(ReportDiagnostic.Warn, compilation.Options.SpecificDiagnosticOptions("CA1001")) End Using End Function @@ -154,7 +160,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests Public Async Function TestSourceGeneratorOutputIncludedInCompilation() As Task Dim sourceGeneratorLocation = GetType(TestSourceGenerator.HelloWorldGenerator).Assembly.Location - Dim compilerInvocation = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" + Dim project = Await Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.CompilerInvocation.CreateFromJsonAsync(" { ""tool"": ""csc"", ""arguments"": ""/noconfig /analyzer:\""" + sourceGeneratorLocation.Replace("\", "\\") + "\"" /out:Output.dll"", @@ -162,7 +168,8 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests ""sourceRootPath"": ""F:\\"" }") - Dim generatedTrees = compilerInvocation.Compilation.SyntaxTrees + Dim compilation = Await project.GetCompilationAsync() + Dim generatedTrees = compilation.SyntaxTrees Assert.Single(generatedTrees, Function(t) t.FilePath.EndsWith(TestSourceGenerator.HelloWorldGenerator.GeneratedEnglishClassName + ".cs")) Assert.Single(generatedTrees, Function(t) t.FilePath.EndsWith(TestSourceGenerator.HelloWorldGenerator.GeneratedSpanishClassName + ".cs")) diff --git a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb index 221a30c89f792..97cb52a158596 100644 --- a/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb +++ b/src/Features/Lsif/GeneratorTest/Utilities/TestLsifOutput.vb @@ -52,7 +52,7 @@ Namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat.Generator.UnitTests.U ' Assert we don't have any errors to prevent any typos in the tests Assert.Empty(compilation.GetDiagnostics().Where(Function(d) d.Severity = DiagnosticSeverity.Error)) - Await lsifGenerator.GenerateForCompilationAsync(compilation, project.FilePath, project.Services, GeneratorOptions.Default) + Await lsifGenerator.GenerateForProjectAsync(project, GeneratorOptions.Default) Next End Function