private static async Task GenerateAsync( FileInfo?solution, FileInfo?project, FileInfo?compilerInvocation, string?output, LsifFormat outputFormat, string?log ) { // If we have an output file, we'll write to that, else we'll use Console.Out using var outputFile = output != null ? new StreamWriter(output) : null; var outputWriter = outputFile ?? Console.Out; using var logFile = log != null ? new StreamWriter(log) : TextWriter.Null; ILsifJsonWriter lsifWriter = outputFormat switch { LsifFormat.Json => new JsonModeLsifJsonWriter(outputWriter), LsifFormat.Line => new LineModeLsifJsonWriter(outputWriter), _ => throw new NotImplementedException() }; try { // Exactly one of "solution", or "project" or "compilerInvocation" should be specified if (solution != null && project == null && compilerInvocation == null) { await GenerateFromSolutionAsync(solution, lsifWriter, logFile); } else if (solution == null && project != null && compilerInvocation == null) { await GenerateFromProjectAsync(project, lsifWriter, logFile); } else if (solution == null && project == null && compilerInvocation != null) { await GenerateFromCompilerInvocationAsync( compilerInvocation, lsifWriter, logFile ); } else { throw new Exception( "Exactly one of either a solution path, project path or a compiler invocation path should be supplied." ); } } catch (Exception e) { // If it failed, write out to the logs and error, but propagate the error too var message = "Unhandled exception: " + e.ToString(); await logFile.WriteLineAsync(message); Console.Error.WriteLine(message); throw; } (lsifWriter as IDisposable)?.Dispose(); await logFile.WriteLineAsync("Generation complete."); }
private static async Task GenerateFromCompilerInvocationAsync( FileInfo compilerInvocationFile, ILsifJsonWriter lsifWriter, TextWriter logFile ) { await logFile.WriteLineAsync( $"Processing compiler invocation from {compilerInvocationFile.FullName}..." ); var compilerInvocationLoadStopwatch = Stopwatch.StartNew(); var compilerInvocation = 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 = new Generator(lsifWriter); lsifGenerator.GenerateForCompilation( compilerInvocation.Compilation, compilerInvocation.ProjectFilePath, compilerInvocation.LanguageServices, compilerInvocation.Options ); await logFile.WriteLineAsync( $"Generation for {compilerInvocation.ProjectFilePath} completed in {generationStopwatch.Elapsed.ToDisplayString()}." ); }
public Id <T> GetResultId <T>(string edgeKind, Func <T> vertexCreator, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) where T : Vertex { lock (_edgeKindToVertexId) { if (_edgeKindToVertexId.TryGetValue(edgeKind, out var existingId)) { if (!existingId.HasValue) { throw new Exception($"This ResultSet already has an edge of {edgeKind} as {nameof(ResultSetNeedsInformationalEdgeAdded)} was called with this edge kind."); } // TODO: this is a violation of the type system here, really: we're assuming that all calls to this function with the same edge kind // will have the same type parameter. If that's violated, the Id returned here isn't really the right type. return(new Id <T>(existingId.Value.NumericId)); } var vertex = vertexCreator(); _edgeKindToVertexId.Add(edgeKind, vertex.GetId().As <T, Vertex>()); lsifJsonWriter.Write(vertex); lsifJsonWriter.Write(Edge.Create(edgeKind, Id, vertex.GetId(), idFactory)); return(vertex.GetId()); } }
private static async Task GenerateFromBinaryLogAsync(FileInfo binLog, ILsifJsonWriter lsifWriter, TextWriter logFile) { await logFile.WriteLineAsync($"Reading binlog {binLog.FullName}..."); var msbuildInvocations = CompilerInvocationsReader.ReadInvocations(binLog.FullName).ToImmutableArray(); await logFile.WriteLineAsync($"Load of the binlog complete; {msbuildInvocations.Length} invocations were found."); var lsifGenerator = Generator.CreateAndWriteCapabilitiesVertex(lsifWriter); foreach (var msbuildInvocation in msbuildInvocations) { // Convert from the MSBuild "CompilerInvocation" type to our type that we use for our JSON-input mode already. var invocationInfo = new CompilerInvocation.CompilerInvocationInfo { Arguments = msbuildInvocation.CommandLineArguments, ProjectFilePath = msbuildInvocation.ProjectFilePath, Tool = msbuildInvocation.Language == Microsoft.Build.Logging.StructuredLogger.CompilerInvocation.CSharp ? "csc" : "vbc" }; var compilerInvocation = 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()}."); } }
private static async Task GenerateFromSolutionAsync(FileInfo solutionFile, ILsifJsonWriter lsifWriter, TextWriter logFile) { await LocateAndRegisterMSBuild(logFile); await GenerateWithMSBuildLocatedAsync( solutionFile, lsifWriter, logFile, w => w.OpenSolutionAsync(solutionFile.FullName)); }
public SymbolHoldingResultSetTracker( ILsifJsonWriter lsifJsonWriter, Compilation sourceCompilation, IdFactory idFactory ) { _lsifJsonWriter = lsifJsonWriter; _sourceCompilation = sourceCompilation; _idFactory = idFactory; }
public static Generator CreateAndWriteCapabilitiesVertex(ILsifJsonWriter lsifJsonWriter) { var generator = new Generator(lsifJsonWriter); var capabilitiesVertex = new Capabilities(generator._idFactory, HoverProvider, DeclarationProvider, DefinitionProvider, ReferencesProvider, TypeDefinitionProvider, DocumentSymbolProvider, FoldingRangeProvider, DiagnosticProvider); generator._lsifJsonWriter.Write(capabilitiesVertex); return(generator); }
private static async Task GenerateFromProjectAsync(FileInfo projectFile, ILsifJsonWriter lsifWriter, TextWriter logFile) { await LocateAndRegisterMSBuild(logFile); await GenerateWithMSBuildLocatedAsync( projectFile, lsifWriter, logFile, async w => { var project = await w.OpenProjectAsync(projectFile.FullName); return(project.Solution); }); }
/// <summary> /// Generates the LSIF content for a single document. /// </summary> /// <returns>The ID of the outputted Document vertex.</returns> /// <remarks> /// The high level algorithm here is we are going to walk across each token, produce a <see cref="Graph.Range"/> for that token's span, /// bind that token, and then link up the various features. So we'll link that range to the symbols it defines or references, /// will link it to results like Quick Info, and more. This method has a <paramref name="topLevelSymbolsResultSetTracker"/> that /// lets us link symbols across files, and will only talk about "top level" symbols that aren't things like locals that can't /// leak outside a file. /// </remarks> private static async Task <Id <Graph.LsifDocument> > GenerateForDocumentAsync( SemanticModel semanticModel, HostLanguageServices languageServices, GeneratorOptions options, IResultSetTracker topLevelSymbolsResultSetTracker, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) { var syntaxTree = semanticModel.SyntaxTree; var sourceText = semanticModel.SyntaxTree.GetText(); var syntaxFactsService = languageServices.GetRequiredService <ISyntaxFactsService>(); var semanticFactsService = languageServices.GetRequiredService <ISemanticFactsService>(); string?contentBase64Encoded = null; var uri = syntaxTree.FilePath; // 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 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:///" + syntaxTree.FilePath.Replace('\\', '/'); } 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)); // 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 // the topLevelSymbolsResultSetTracker, so we'll make another tracker for document local symbols, and then have a delegating // one that picks the correct one of the two. var documentLocalSymbolsResultSetTracker = new SymbolHoldingResultSetTracker(lsifJsonWriter, semanticModel.Compilation, idFactory); var symbolResultsTracker = new DelegatingResultSetTracker(symbol => { if (symbol.Kind is SymbolKind.Local or SymbolKind.RangeVariable or SymbolKind.Label) { // These symbols can go in the document local one because they can't escape methods return(documentLocalSymbolsResultSetTracker); }
private static async Task GenerateWithMSBuildLocatedAsync( FileInfo solutionOrProjectFile, ILsifJsonWriter lsifWriter, TextWriter logFile, Func <MSBuildWorkspace, Task <Solution> > openAsync) { await logFile.WriteLineAsync($"Loading {solutionOrProjectFile.FullName}..."); var solutionLoadStopwatch = Stopwatch.StartNew(); var msbuildWorkspace = MSBuildWorkspace.Create(); msbuildWorkspace.WorkspaceFailed += (s, e) => logFile.WriteLine("Error while loading: " + e.Diagnostic.Message); var solution = await openAsync(msbuildWorkspace); var options = GeneratorOptions.Default; await logFile.WriteLineAsync($"Load completed in {solutionLoadStopwatch.Elapsed.ToDisplayString()}."); var lsifGenerator = Generator.CreateAndWriteCapabilitiesVertex(lsifWriter); var totalTimeInGenerationAndCompilationFetchStopwatch = Stopwatch.StartNew(); var totalTimeInGenerationPhase = TimeSpan.Zero; foreach (var project in solution.Projects) { if (project.SupportsCompilation && project.FilePath != null) { var compilationCreationStopwatch = Stopwatch.StartNew(); var compilation = (await project.GetCompilationAsync()) !; 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.LanguageServices, options); generationForProjectStopwatch.Stop(); totalTimeInGenerationPhase += generationForProjectStopwatch.Elapsed; await logFile.WriteLineAsync($"Generation for {project.FilePath} completed in {generationForProjectStopwatch.Elapsed.ToDisplayString()}."); } } await logFile.WriteLineAsync($"Total time spent in the generation phase for all projects, excluding compilation fetch time: {totalTimeInGenerationPhase.ToDisplayString()}"); await logFile.WriteLineAsync($"Total time spent in the generation phase for all projects, including compilation fetch time: {totalTimeInGenerationAndCompilationFetchStopwatch.Elapsed.ToDisplayString()}"); }
private static async Task GenerateFromSolutionAsync(FileInfo solutionFile, ILsifJsonWriter lsifWriter, TextWriter logFile) { // Make sure we pick the highest version var msbuildInstance = MSBuildLocator.QueryVisualStudioInstances().OrderByDescending(i => i.Version).FirstOrDefault(); if (msbuildInstance == null) { throw new Exception("No MSBuild instances installed with Visual Studio could be found."); } else { await logFile.WriteLineAsync($"Using the MSBuild instance located at {msbuildInstance.MSBuildPath}."); } MSBuildLocator.RegisterInstance(msbuildInstance); await GenerateFromSolutionWithMSBuildLocatedAsync(solutionFile, lsifWriter, logFile); }
public BatchingLsifJsonWriter(ILsifJsonWriter underlyingWriter) { _underlyingWriter = underlyingWriter; }
/// <summary> /// Generates the LSIF content for a single document. /// </summary> /// <returns>The ID of the outputted Document vertex.</returns> /// <remarks> /// The high level algorithm here is we are going to walk across each token, produce a <see cref="Graph.Range"/> for that token's span, /// bind that token, and then link up the various features. So we'll link that range to the symbols it defines or references, /// will link it to results like Quick Info, and more. This method has a <paramref name="topLevelSymbolsResultSetTracker"/> that /// lets us link symbols across files, and will only talk about "top level" symbols that aren't things like locals that can't /// leak outside a file. /// </remarks> private static Id <Graph.Document> GenerateForDocument( SemanticModel semanticModel, HostLanguageServices languageServices, IResultSetTracker topLevelSymbolsResultSetTracker, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) { var syntaxTree = semanticModel.SyntaxTree; var sourceText = semanticModel.SyntaxTree.GetText(); var syntaxFactsService = languageServices.GetRequiredService <ISyntaxFactsService>(); var semanticFactsService = languageServices.GetRequiredService <ISemanticFactsService>(); var documentVertex = new Graph.Document(new Uri(syntaxTree.FilePath), GetLanguageKind(semanticModel.Language), idFactory); lsifJsonWriter.Write(documentVertex); lsifJsonWriter.Write(new Event(Event.EventKind.Begin, documentVertex.GetId(), idFactory)); // 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 // the topLevelSymbolsResultSetTracker, so we'll make another tracker for document local symbols, and then have a delegating // one that picks the correct one of the two. var documentLocalSymbolsResultSetTracker = new SymbolHoldingResultSetTracker(lsifJsonWriter, semanticModel.Compilation, idFactory); var symbolResultsTracker = new DelegatingResultSetTracker(symbol => { if (symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.RangeVariable || symbol.Kind == SymbolKind.Label) { // These symbols can go in the document local one because they can't escape methods return(documentLocalSymbolsResultSetTracker); } else if (symbol.ContainingType != null && symbol.DeclaredAccessibility == Accessibility.Private && symbol.ContainingType.Locations.Length == 1) { // This is a private member in a class that isn't partial, so it can't escape the file return(documentLocalSymbolsResultSetTracker); } else { return(topLevelSymbolsResultSetTracker); } }); // We will walk the file token-by-token, making a range for each one and then attaching information for it var rangeVertices = new List <Id <Graph.Range> >(); 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, // so we'll just make it Lazy. var lazyRangeVertex = new Lazy <Graph.Range>(() => { var rangeVertex = Graph.Range.FromTextSpan(syntaxToken.Span, sourceText, idFactory); lsifJsonWriter.Write(rangeVertex); rangeVertices.Add(rangeVertex.GetId()); return(rangeVertex); }, LazyThreadSafetyMode.None); var declaredSymbol = semanticFactsService.GetDeclaredSymbol(semanticModel, syntaxToken, CancellationToken.None); ISymbol?referencedSymbol = null; if (syntaxFactsService.IsBindableToken(syntaxToken)) { var bindableParent = syntaxFactsService.TryGetBindableParent(syntaxToken); if (bindableParent != null) { var symbolInfo = semanticModel.GetSymbolInfo(bindableParent); if (symbolInfo.Symbol != null && IncludeSymbolInReferences(symbolInfo.Symbol)) { referencedSymbol = symbolInfo.Symbol; } } } if (declaredSymbol != null || referencedSymbol != null) { // For now, we will link the range to the original definition, preferring the definition, as this is the symbol // that would be used if we invoke a feature on this range. This is analogous to the logic in // SymbolFinder.FindSymbolAtPositionAsync where if a token is both a reference and definition we'll prefer the // definition. Once we start supporting hover we'll hae to remove the "original defintion" part of this, since // since we show different contents for different constructed types there. var symbolForLinkedResultSet = (declaredSymbol ?? referencedSymbol) !.OriginalDefinition; var symbolForLinkedResultSetId = symbolResultsTracker.GetResultSetIdForSymbol(symbolForLinkedResultSet); lsifJsonWriter.Write(Edge.Create("next", lazyRangeVertex.Value.GetId(), symbolForLinkedResultSetId, idFactory)); if (declaredSymbol != null) { var definitionResultsId = symbolResultsTracker.GetResultIdForSymbol(declaredSymbol, Methods.TextDocumentDefinitionName, () => new DefinitionResult(idFactory)); lsifJsonWriter.Write(new Item(definitionResultsId.As <DefinitionResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory)); } if (referencedSymbol != null) { // Create the link from the references back to this range. Note: this range can be reference to a // symbol but the range can point a different symbol's resultSet. This can happen if the token is // both a definition of a symbol (where we will point to the definition) but also a reference to some // other symbol. var referenceResultsId = symbolResultsTracker.GetResultIdForSymbol(referencedSymbol.OriginalDefinition, Methods.TextDocumentReferencesName, () => new ReferenceResult(idFactory)); lsifJsonWriter.Write(new Item(referenceResultsId.As <ReferenceResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory, property: "references")); } } } lsifJsonWriter.Write(Edge.Create("contains", documentVertex.GetId(), rangeVertices, idFactory)); lsifJsonWriter.Write(new Event(Event.EventKind.End, documentVertex.GetId(), idFactory)); return(documentVertex.GetId()); }
public Generator(ILsifJsonWriter lsifJsonWriter) { _lsifJsonWriter = lsifJsonWriter; }
private static async Task GenerateFromSolutionWithMSBuildLocatedAsync(FileInfo solutionFile, ILsifJsonWriter lsifWriter, TextWriter logFile) { await logFile.WriteLineAsync($"Loading {solutionFile.FullName}..."); var solutionLoadStopwatch = Stopwatch.StartNew(); var msbuildWorkspace = MSBuildWorkspace.Create(); var solution = await msbuildWorkspace.OpenSolutionAsync(solutionFile.FullName); await logFile.WriteLineAsync($"Load of the solution completed in {solutionLoadStopwatch.Elapsed.ToDisplayString()}."); var lsifGenerator = new Generator(lsifWriter); var totalTimeInGenerationAndCompilationFetchStopwatch = Stopwatch.StartNew(); var totalTimeInGenerationPhase = TimeSpan.Zero; foreach (var project in solution.Projects) { if (project.SupportsCompilation && project.FilePath != null) { var compilationCreationStopwatch = Stopwatch.StartNew(); var compilation = (await project.GetCompilationAsync()) !; await logFile.WriteLineAsync($"Fetch of compilation for {project.FilePath} completed in {compilationCreationStopwatch.Elapsed.ToDisplayString()}."); var generationForProjectStopwatch = Stopwatch.StartNew(); lsifGenerator.GenerateForCompilation(compilation, project.FilePath, project.LanguageServices, project.Solution.Options); generationForProjectStopwatch.Stop(); totalTimeInGenerationPhase += generationForProjectStopwatch.Elapsed; await logFile.WriteLineAsync($"Generation for {project.FilePath} completed in {generationForProjectStopwatch.Elapsed.ToDisplayString()}."); } } await logFile.WriteLineAsync($"Total time spent in the generation phase for all projects, excluding compilation fetch time: {totalTimeInGenerationPhase.ToDisplayString()}"); await logFile.WriteLineAsync($"Total time spent in the generation phase for all projects, including compilation fetch time: {totalTimeInGenerationAndCompilationFetchStopwatch.Elapsed.ToDisplayString()}"); }
/// <summary> /// Generates the LSIF content for a single document. /// </summary> /// <returns>The ID of the outputted Document vertex.</returns> /// <remarks> /// The high level algorithm here is we are going to walk across each token, produce a <see cref="Graph.Range"/> for that token's span, /// bind that token, and then link up the various features. So we'll link that range to the symbols it defines or references, /// will link it to results like Quick Info, and more. This method has a <paramref name="topLevelSymbolsResultSetTracker"/> that /// lets us link symbols across files, and will only talk about "top level" symbols that aren't things like locals that can't /// leak outside a file. /// </remarks> private static Id <Graph.LsifDocument> GenerateForDocument( SemanticModel semanticModel, HostLanguageServices languageServices, OptionSet options, IResultSetTracker topLevelSymbolsResultSetTracker, ILsifJsonWriter lsifJsonWriter, IdFactory idFactory) { var syntaxTree = semanticModel.SyntaxTree; var sourceText = semanticModel.SyntaxTree.GetText(); var syntaxFactsService = languageServices.GetRequiredService <ISyntaxFactsService>(); var semanticFactsService = languageServices.GetRequiredService <ISemanticFactsService>(); string?contentBase64Encoded = null; // 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 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 allocatios here? contentBase64Encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(text.ToString())); } var documentVertex = new Graph.LsifDocument(new Uri(syntaxTree.FilePath, UriKind.RelativeOrAbsolute), GetLanguageKind(semanticModel.Language), contentBase64Encoded, idFactory); lsifJsonWriter.Write(documentVertex); lsifJsonWriter.Write(new Event(Event.EventKind.Begin, documentVertex.GetId(), idFactory)); // 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 // the topLevelSymbolsResultSetTracker, so we'll make another tracker for document local symbols, and then have a delegating // one that picks the correct one of the two. var documentLocalSymbolsResultSetTracker = new SymbolHoldingResultSetTracker(lsifJsonWriter, semanticModel.Compilation, idFactory); var symbolResultsTracker = new DelegatingResultSetTracker(symbol => { if (symbol.Kind == SymbolKind.Local || symbol.Kind == SymbolKind.RangeVariable || symbol.Kind == SymbolKind.Label) { // These symbols can go in the document local one because they can't escape methods return(documentLocalSymbolsResultSetTracker); } else if (symbol.ContainingType != null && symbol.DeclaredAccessibility == Accessibility.Private && symbol.ContainingType.Locations.Length == 1) { // This is a private member in a class that isn't partial, so it can't escape the file return(documentLocalSymbolsResultSetTracker); } else { return(topLevelSymbolsResultSetTracker); } }); // We will walk the file token-by-token, making a range for each one and then attaching information for it var rangeVertices = new List <Id <Graph.Range> >(); 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, // so we'll just make it Lazy. var lazyRangeVertex = new Lazy <Graph.Range>(() => { var rangeVertex = Graph.Range.FromTextSpan(syntaxToken.Span, sourceText, idFactory); lsifJsonWriter.Write(rangeVertex); rangeVertices.Add(rangeVertex.GetId()); return(rangeVertex); }, LazyThreadSafetyMode.None); var declaredSymbol = semanticFactsService.GetDeclaredSymbol(semanticModel, syntaxToken, CancellationToken.None); ISymbol?referencedSymbol = null; if (syntaxFactsService.IsBindableToken(syntaxToken)) { var bindableParent = syntaxFactsService.TryGetBindableParent(syntaxToken); if (bindableParent != null) { var symbolInfo = semanticModel.GetSymbolInfo(bindableParent); if (symbolInfo.Symbol != null && IncludeSymbolInReferences(symbolInfo.Symbol)) { referencedSymbol = symbolInfo.Symbol; } } } if (declaredSymbol != null || referencedSymbol != null) { // For now, we will link the range to the original definition, preferring the definition, as this is the symbol // that would be used if we invoke a feature on this range. This is analogous to the logic in // SymbolFinder.FindSymbolAtPositionAsync where if a token is both a reference and definition we'll prefer the // definition. Once we start supporting hover we'll have to remove the "original definition" part of this, since // since we show different contents for different constructed types there. var symbolForLinkedResultSet = (declaredSymbol ?? referencedSymbol) !.OriginalDefinition; var symbolForLinkedResultSetId = symbolResultsTracker.GetResultSetIdForSymbol(symbolForLinkedResultSet); lsifJsonWriter.Write(Edge.Create("next", lazyRangeVertex.Value.GetId(), symbolForLinkedResultSetId, idFactory)); if (declaredSymbol != null) { var definitionResultsId = symbolResultsTracker.GetResultIdForSymbol(declaredSymbol, Methods.TextDocumentDefinitionName, () => new DefinitionResult(idFactory)); lsifJsonWriter.Write(new Item(definitionResultsId.As <DefinitionResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory)); } if (referencedSymbol != null) { // Create the link from the references back to this range. Note: this range can be reference to a // symbol but the range can point a different symbol's resultSet. This can happen if the token is // both a definition of a symbol (where we will point to the definition) but also a reference to some // other symbol. var referenceResultsId = symbolResultsTracker.GetResultIdForSymbol(referencedSymbol.OriginalDefinition, Methods.TextDocumentReferencesName, () => new ReferenceResult(idFactory)); lsifJsonWriter.Write(new Item(referenceResultsId.As <ReferenceResult, Vertex>(), lazyRangeVertex.Value.GetId(), documentVertex.GetId(), idFactory, property: "references")); } // Write hover information for the symbol, if edge has not already been added. // 'textDocument/hover' edge goes from the symbol ResultSet vertex to the hover result // See https://github.com/Microsoft/language-server-protocol/blob/main/indexFormat/specification.md#resultset for an example. if (symbolResultsTracker.ResultSetNeedsInformationalEdgeAdded(symbolForLinkedResultSet, Methods.TextDocumentHoverName)) { // TODO: Can we avoid the WaitAndGetResult_CanCallOnBackground call by adding a sync method to compute hover? var hover = HoverHandler.GetHoverAsync(semanticModel, syntaxToken.SpanStart, languageServices, CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None); if (hover != null) { var hoverResult = new HoverResult(hover, idFactory); lsifJsonWriter.Write(hoverResult); lsifJsonWriter.Write(Edge.Create(Methods.TextDocumentHoverName, symbolForLinkedResultSetId, hoverResult.GetId(), idFactory)); } } } } lsifJsonWriter.Write(Edge.Create("contains", documentVertex.GetId(), rangeVertices, idFactory)); // Write the folding ranges for the document. var foldingRanges = FoldingRangesHandler.GetFoldingRanges(syntaxTree, languageServices, options, isMetadataAsSource: false, CancellationToken.None); var foldingRangeResult = new FoldingRangeResult(foldingRanges, idFactory); lsifJsonWriter.Write(foldingRangeResult); lsifJsonWriter.Write(Edge.Create(Methods.TextDocumentFoldingRangeName, documentVertex.GetId(), foldingRangeResult.GetId(), idFactory)); lsifJsonWriter.Write(new Event(Event.EventKind.End, documentVertex.GetId(), idFactory)); return(documentVertex.GetId()); }
private Generator(ILsifJsonWriter lsifJsonWriter) { _lsifJsonWriter = lsifJsonWriter; }