Exemple #1
0
        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.");
        }
Exemple #2
0
        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());
                }
            }
Exemple #4
0
        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()}.");
            }
        }
Exemple #5
0
 private static async Task GenerateFromSolutionAsync(FileInfo solutionFile, ILsifJsonWriter lsifWriter, TextWriter logFile)
 {
     await LocateAndRegisterMSBuild(logFile);
     await GenerateWithMSBuildLocatedAsync(
         solutionFile, lsifWriter, logFile,
         w => w.OpenSolutionAsync(solutionFile.FullName));
 }
Exemple #6
0
 public SymbolHoldingResultSetTracker(
     ILsifJsonWriter lsifJsonWriter,
     Compilation sourceCompilation,
     IdFactory idFactory
     )
 {
     _lsifJsonWriter    = lsifJsonWriter;
     _sourceCompilation = sourceCompilation;
     _idFactory         = idFactory;
 }
Exemple #7
0
        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);
        }
Exemple #8
0
 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);
     });
 }
Exemple #9
0
        /// <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);
                }
Exemple #10
0
        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()}");
        }
Exemple #11
0
        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;
 }
Exemple #15
0
        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()}");
        }
Exemple #16
0
        /// <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());
        }
Exemple #17
0
 private Generator(ILsifJsonWriter lsifJsonWriter)
 {
     _lsifJsonWriter = lsifJsonWriter;
 }