├── LsifDotnet.Benchmark ├── Program.cs ├── LsifDotnet.Benchmark.csproj └── SelfEmit.cs ├── .dockerignore ├── LsifDotnet.sln.DotSettings ├── .github └── workflows │ ├── sourcegraph.yml │ ├── dotnet.yml │ ├── Release.yml │ └── docker-publish.yml ├── LsifDotnet ├── Roslyn │ ├── IdentifierCollectorFactory.cs │ └── CSharpIdentifierCollector.cs ├── Dockerfile ├── LsifDotnet.csproj ├── GraphBuilder.cs ├── Program.cs ├── IndexHandler.cs ├── Lsif │ ├── LsifItem.cs │ ├── LegacyLsifIndexer.cs │ └── DataFlowLsifIndexer.cs └── MarkdownHelper.cs ├── LsifDotnet.Tests ├── LsifDotnet.Tests.csproj └── KeywordTest.cs ├── tools └── verify_lsif.py ├── LsifDotnet.sln ├── .gitattributes ├── README.md ├── .gitignore └── LICENSE /LsifDotnet.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using BenchmarkDotNet.Running; 3 | 4 | namespace LsifDotnet.Benchmark; 5 | 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | Trace.Listeners.Add(new ConsoleTraceListener()); 11 | var summary = BenchmarkRunner.Run(typeof(Program).Assembly); 12 | } 13 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /LsifDotnet.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /.github/workflows/sourcegraph.yml: -------------------------------------------------------------------------------- 1 | name: Generate LSIF and push to sourcegraph 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | lsif-dotnet: 8 | runs-on: ubuntu-latest 9 | container: ghcr.io/tcz717/lsifdotnet:main 10 | steps: 11 | - uses: actions/checkout@v2.4.0 12 | - name: Restore dependencies 13 | run: dotnet restore 14 | - name: Generate LSIF data 15 | run: /app/lsif-dotnet 16 | - name: Upload LSIF data 17 | run: src lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /LsifDotnet/Roslyn/IdentifierCollectorFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace LsifDotnet.Roslyn; 4 | 5 | public class IdentifierCollectorFactory 6 | { 7 | public CSharpIdentifierCollector CreateInstance() 8 | { 9 | return new CSharpIdentifierCollector(LoggerFactory.CreateLogger()); 10 | } 11 | 12 | public IdentifierCollectorFactory(ILoggerFactory loggerFactory) 13 | { 14 | LoggerFactory = loggerFactory; 15 | } 16 | 17 | public ILoggerFactory LoggerFactory { get; } 18 | } -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 6.0.x 20 | - name: Restore dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --no-restore 24 | - name: Test 25 | run: dotnet test --no-build --verbosity normal 26 | -------------------------------------------------------------------------------- /LsifDotnet/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS base 4 | WORKDIR /app 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 7 | ARG VERSION=1.0.0 8 | WORKDIR /src 9 | COPY ["LsifDotnet/LsifDotnet.csproj", "LsifDotnet/"] 10 | RUN dotnet restore "LsifDotnet/LsifDotnet.csproj" 11 | COPY . . 12 | WORKDIR "/src/LsifDotnet" 13 | RUN dotnet publish "LsifDotnet.csproj" -c Release -o /app/publish -p:Version=${VERSION} 14 | 15 | FROM sourcegraph/src-cli:3.34.1 AS src-cli 16 | 17 | FROM base AS final 18 | WORKDIR /app 19 | COPY --from=build /app/publish . 20 | COPY --from=src-cli /usr/bin/src /usr/bin/ -------------------------------------------------------------------------------- /LsifDotnet.Benchmark/LsifDotnet.Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LsifDotnet.Tests/LsifDotnet.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | all 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tools/verify_lsif.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | 4 | if __name__ == "__main__": 5 | with open(sys.argv[1], "r") as f: 6 | last_id = 0 7 | total_violation = 0 8 | indexed = set() 9 | for line in f.readlines(): 10 | item = json.loads(line) 11 | if item["id"] <= last_id: 12 | print("\033[33mWarn\33[0m: id not increasing:", item["id"]) 13 | print("The line is: {line}, last id: {last_id}".format(line=line, last_id=last_id)) 14 | total_violation += 1 15 | last_id = item["id"] 16 | 17 | if "inV" in item and not item["inV"] in indexed: 18 | print("\033[31mError\33[0m: inV not indexed:", item["inV"]) 19 | print("The line is: {line}, last id: {last_id}".format(line=line, last_id=last_id)) 20 | total_violation += 1 21 | if "outV" in item and not item["outV"] in indexed: 22 | print("\033[31mError\33[0m: outV not indexed:", item["outV"]) 23 | print("The line is: {line}, last id: {last_id}".format(line=line, last_id=last_id)) 24 | total_violation += 1 25 | if "inVs" in item and not indexed.issuperset(item["inVs"]): 26 | print("\033[31mError\33[0m: inVs not indexed:", item["inVs"]) 27 | print("The line is: {line}, last id: {last_id}".format(line=line, last_id=last_id)) 28 | total_violation += 1 29 | 30 | indexed.add(item["id"]) 31 | 32 | print("Total violation:", total_violation) -------------------------------------------------------------------------------- /LsifDotnet.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32104.313 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LsifDotnet", "LsifDotnet\LsifDotnet.csproj", "{B426E5B4-869F-4B5B-A9DE-DAC355A98301}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LsifDotnet.Tests", "LsifDotnet.Tests\LsifDotnet.Tests.csproj", "{0F90A056-0000-49A9-86F7-99A4F7285C41}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LsifDotnet.Benchmark", "LsifDotnet.Benchmark\LsifDotnet.Benchmark.csproj", "{A428D717-EE2A-490D-BD4A-A902F8C05F13}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B426E5B4-869F-4B5B-A9DE-DAC355A98301}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {B426E5B4-869F-4B5B-A9DE-DAC355A98301}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {B426E5B4-869F-4B5B-A9DE-DAC355A98301}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {B426E5B4-869F-4B5B-A9DE-DAC355A98301}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {0F90A056-0000-49A9-86F7-99A4F7285C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {0F90A056-0000-49A9-86F7-99A4F7285C41}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {0F90A056-0000-49A9-86F7-99A4F7285C41}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {0F90A056-0000-49A9-86F7-99A4F7285C41}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {A428D717-EE2A-490D-BD4A-A902F8C05F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {A428D717-EE2A-490D-BD4A-A902F8C05F13}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {A428D717-EE2A-490D-BD4A-A902F8C05F13}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {A428D717-EE2A-490D-BD4A-A902F8C05F13}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {39F2171A-DDEB-4C24-8D7E-43FCB765F1C5} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /.github/workflows/Release.yml: -------------------------------------------------------------------------------- 1 | name: .NET Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | # Sequence of patterns matched against refs/tags 7 | tags: 8 | - 'v?[0-9]+.[0-9]+.[0-9]+**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | os: [win, linux, osx] 16 | 17 | steps: 18 | - name: Get Version 19 | id: get_version 20 | uses: battila7/get-version-action@v2.2.1 21 | - uses: actions/checkout@v2 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v1 24 | with: 25 | dotnet-version: 6.0.x 26 | - name: Restore dependencies 27 | run: dotnet restore 28 | - name: Build 29 | run: dotnet build --no-restore 30 | - name: Test 31 | run: dotnet test --no-build --verbosity normal 32 | - name: Publish 33 | run: dotnet publish -c release -o publish/${{ matrix.os }} --os ${{ matrix.os }} -p:Version=${{ steps.get_version.outputs.version-without-v }} --verbosity normal 34 | - name: Zip Release 35 | # You may pin to the exact commit or the version. 36 | # uses: TheDoctor0/zip-release@591e9b128012d3328db6043d0d0266c3ac27f9b5 37 | uses: TheDoctor0/zip-release@0.6.1 38 | with: 39 | # Filename for archive 40 | filename: lsif-dotnet-${{ matrix.os }}-${{ steps.get_version.outputs.version-without-v }}.zip 41 | # Base path for archive files 42 | path: ${{ matrix.os }}/* 43 | # Working directory before zipping 44 | directory: publish/ 45 | - name: GH Release 46 | # You may pin to the exact commit or the version. 47 | # uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 48 | uses: softprops/action-gh-release@v0.1.14 49 | with: 50 | files: publish/*.zip 51 | 52 | push-nuget: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Get Version 56 | id: get_version 57 | uses: battila7/get-version-action@v2.2.1 58 | - uses: actions/checkout@v2 59 | - name: Setup .NET 60 | uses: actions/setup-dotnet@v1 61 | with: 62 | dotnet-version: 6.0.x 63 | - name: Restore dependencies 64 | run: dotnet restore 65 | - name: Pack 66 | run: dotnet pack -c release -p:Version=${{ steps.get_version.outputs.version-without-v }} -p:PackageId=LsifDotnet --verbosity normal LsifDotnet/LsifDotnet.csproj 67 | - name: Publish Nuget 68 | run: dotnet nuget push LsifDotnet/nupkg/ --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json 69 | -------------------------------------------------------------------------------- /LsifDotnet/LsifDotnet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | lsif-dotnet 7 | enable 8 | tcz717 9 | https://github.com/tcz717/LsifDotnet/blob/main/LICENSE 10 | https://github.com/tcz717/LsifDotnet 11 | README.md 12 | https://github.com/tcz717/LsifDotnet 13 | roslyn;lsif;lsif-indexer;lsif-generator;lsif-dump;lsif-dotnet 14 | LICENSE 15 | True 16 | Linux 17 | 18 | True 19 | dotnet-lsif 20 | ./nupkg 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | True 42 | \ 43 | 44 | 45 | 46 | 47 | 48 | True 49 | \ 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LsifDotnet [![.NET 6.0](https://github.com/tcz717/LsifDotnet/actions/workflows/dotnet.yml/badge.svg)](https://github.com/tcz717/LsifDotnet/actions/workflows/dotnet.yml)![Nuget](https://img.shields.io/nuget/v/LsifDotnet)![Nuget](https://img.shields.io/nuget/dt/LsifDotnet?label=nuget%20downloads)![GitHub all releases](https://img.shields.io/github/downloads/tcz717/LsifDotnet/total?label=GitHub%20downloads) 2 | 3 | Visit https://lsif.dev/ to learn about LSIF. 4 | 5 | Example scenario: [Sourcegraph precise code intelligence](https://docs.sourcegraph.com/code_intelligence/explanations/precise_code_intelligence) ([lsif-dotnet repo](https://sourcegraph.com/github.com/tcz717/LsifDotnet/-/blob/LsifDotnet/Program.cs)) 6 | 7 | 8 | Only tested in win platform and don't support `VisualBasic` yet. 9 | 10 | Implmented LSIF data: 11 | 12 | - `textDocument/hover` 13 | - `textDocument/references` 14 | - `textDocument/definition` 15 | - `moniker` 16 | 17 | ### Requirement 18 | 19 | Installed [.net 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) 20 | 21 | ## Install via nuget 22 | [Package Home](https://www.nuget.org/packages/LsifDotnet/) 23 | ```powershell 24 | dotnet tool install --global LsifDotnet 25 | dotnet lsif 26 | ``` 27 | 28 | ### Usage 29 | 30 | ``` 31 | PS C:\Downloads\lsif-dotnet-win-0.0.1-beta6\win> .\lsif-dotnet.exe -h 32 | Description: 33 | An indexer that dumps lsif information from dotnet solution. 34 | 35 | Usage: 36 | lsif-dotnet [] [options] 37 | 38 | Arguments: 39 | The solution to be indexed. Default is the only solution file in the current folder. 40 | 41 | Options: 42 | -o, --output The lsif dump output file. [default: dump.lsif] 43 | -d, --dot Dump graphviz dot file. 44 | -s, --svg Dump graph svg file. (by quickchart.io/graphviz API) 45 | -c, --culture The culture used to show hover info. [default: zh-CN] 46 | -q, --quiet Be more quiet 47 | -p, --parallelism How many hover content generation tasks can be handled at the same time. When 48 | unset or set to 0, use legacy indexer, relative slow but need less memory. 49 | -i, --index The start index of lsif items. Use a different file when concatenating other 50 | lsif files. 51 | -e, --excludedFiles Absolute paths of the files to be excluded in the output,this can be either a 52 | source code file or a project file 53 | --version Show version information 54 | -?, -h, --help Show help and usage information 55 | 56 | ``` 57 | 58 | #### Dump a solution's lsif file 59 | 60 | Goto the folder of the solution file and run: 61 | ```powershell 62 | .\lsif-dotnet.exe 63 | ``` 64 | 65 | And a `dump.lsif` file will be created in the current folder. 66 | 67 | ### Download: 68 | 69 | Github Release: https://github.com/tcz717/LsifDotnet/releases 70 | 71 | ### Next 72 | 73 | - Formated logging 74 | - Linux/OSX verfication 75 | - Unit tests 76 | - VB support 77 | - nuget information extraction 78 | - More lsif features 79 | -------------------------------------------------------------------------------- /LsifDotnet/GraphBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Net.Http.Json; 8 | using System.Threading.Tasks; 9 | using System.Threading.Tasks.Dataflow; 10 | using LsifDotnet.Lsif; 11 | using QuikGraph; 12 | using QuikGraph.Graphviz; 13 | 14 | namespace LsifDotnet; 15 | 16 | public class GraphBuilder 17 | { 18 | public BidirectionalGraph> Graph { get; } = new(); 19 | private readonly Dictionary _vertexDict = new(); 20 | 21 | public void AddLsifItem(LsifItem item) 22 | { 23 | switch (item) 24 | { 25 | case { Type: LsifItemType.Vertex }: 26 | Graph.AddVertex(item); 27 | _vertexDict.Add(item.Id, item); 28 | break; 29 | case SingleEdge singleEdge: 30 | Graph.AddEdge(new EquatableTaggedEdge( 31 | _vertexDict[singleEdge.OutV], 32 | _vertexDict[singleEdge.InV], 33 | singleEdge.Label)); 34 | break; 35 | case ItemEdge itemEdge: 36 | Graph.AddEdgeRange(itemEdge.InVs.Select(inV => new EquatableTaggedEdge( 37 | _vertexDict[itemEdge.OutV], 38 | _vertexDict[inV], 39 | $"Item({itemEdge.Document}) {itemEdge.Property}"))); 40 | break; 41 | case MultipleEdge multipleEdge: 42 | Graph.AddEdgeRange(multipleEdge.InVs.Select(inV => new EquatableTaggedEdge( 43 | _vertexDict[multipleEdge.OutV], 44 | _vertexDict[inV], 45 | multipleEdge.Label))); 46 | break; 47 | default: 48 | throw new ArgumentOutOfRangeException(nameof(item)); 49 | } 50 | } 51 | 52 | public ITargetBlock BuildDataFlowBlock() 53 | { 54 | return new ActionBlock(AddLsifItem); 55 | } 56 | 57 | public async Task SaveDotAsync(string dotFile = "lsif.dot") 58 | { 59 | await File.WriteAllTextAsync(dotFile, ToGraphviz()); 60 | } 61 | 62 | public async Task SaveSvgAsync(string svgFile = "lsif.svg") 63 | { 64 | var client = new HttpClient(); 65 | var responseMessage = await client.PostAsJsonAsync("https://quickchart.io/graphviz", new { graph = ToGraphviz() }); 66 | if (responseMessage.IsSuccessStatusCode) 67 | { 68 | await File.WriteAllTextAsync(svgFile, await responseMessage.Content.ReadAsStringAsync()); 69 | Process.Start(new ProcessStartInfo(svgFile) { UseShellExecute = true }); 70 | } 71 | else 72 | { 73 | Console.WriteLine("Bad svg response"); 74 | } 75 | } 76 | 77 | private string ToGraphviz() 78 | { 79 | return Graph.ToGraphviz(algorithm => 80 | { 81 | algorithm.FormatVertex += 82 | (_, eventArgs) => eventArgs.VertexFormat.Label = eventArgs.Vertex.ToJson(); 83 | algorithm.FormatEdge += (_, eventArgs) => 84 | eventArgs.EdgeFormat.Label.Value = eventArgs.Edge.Tag; 85 | }); 86 | } 87 | 88 | public async IAsyncEnumerable RecordLsifItem(IAsyncEnumerable source) 89 | { 90 | await foreach (var item in source) 91 | { 92 | AddLsifItem(item); 93 | yield return item; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | workflow_dispatch: 10 | push: 11 | branches: [ main ] 12 | # Publish semver tags as releases. 13 | tags: [ 'v*.*.*' ] 14 | pull_request: 15 | branches: [ main ] 16 | 17 | env: 18 | # Use docker.io for Docker Hub if empty 19 | REGISTRY: ghcr.io 20 | # github.repository as / 21 | IMAGE_NAME: ${{ github.repository }} 22 | 23 | 24 | jobs: 25 | build: 26 | 27 | runs-on: ubuntu-latest 28 | permissions: 29 | contents: read 30 | packages: write 31 | # This is used to complete the identity challenge 32 | # with sigstore/fulcio when running outside of PRs. 33 | id-token: write 34 | 35 | steps: 36 | - name: Get Version 37 | if: github.event_name == 'tags' 38 | id: get_version 39 | uses: battila7/get-version-action@v2.2.1 40 | 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Install the cosign tool except on PR 45 | # https://github.com/sigstore/cosign-installer 46 | - name: Install cosign 47 | if: github.event_name != 'pull_request' 48 | uses: sigstore/cosign-installer@1e95c1de343b5b0c23352d6417ee3e48d5bcd422 49 | with: 50 | cosign-release: 'v1.4.0' 51 | 52 | 53 | # Workaround: https://github.com/docker/build-push-action/issues/461 54 | - name: Setup Docker buildx 55 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 56 | 57 | # Login against a Docker registry except on PR 58 | # https://github.com/docker/login-action 59 | - name: Log into registry ${{ env.REGISTRY }} 60 | if: github.event_name != 'pull_request' 61 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 62 | with: 63 | registry: ${{ env.REGISTRY }} 64 | username: ${{ github.actor }} 65 | password: ${{ secrets.GITHUB_TOKEN }} 66 | 67 | # Extract metadata (tags, labels) for Docker 68 | # https://github.com/docker/metadata-action 69 | - name: Extract Docker metadata 70 | id: meta 71 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 72 | with: 73 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 74 | 75 | # Build and push Docker image with Buildx (don't push on PR) 76 | # https://github.com/docker/build-push-action 77 | - name: Build and push Docker image 78 | id: build-and-push 79 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 80 | with: 81 | context: . 82 | file: LsifDotnet/Dockerfile 83 | build-args: VERSION=${{ steps.get_version.outputs.version-without-v || '1.0.0' }} 84 | push: ${{ github.event_name != 'pull_request' }} 85 | tags: ${{ steps.meta.outputs.tags }} 86 | labels: ${{ steps.meta.outputs.labels }} 87 | 88 | # Sign the resulting Docker image digest except on PRs. 89 | # This will only write to the public Rekor transparency log when the Docker 90 | # repository is public to avoid leaking data. If you would like to publish 91 | # transparency data even for private images, pass --force to cosign below. 92 | # https://github.com/sigstore/cosign 93 | # - name: Sign the published Docker image 94 | # if: ${{ github.event_name != 'pull_request' }} 95 | # env: 96 | # COSIGN_EXPERIMENTAL: "true" 97 | # # This step uses the identity token to provision an ephemeral certificate 98 | # # against the sigstore community Fulcio instance. 99 | # run: cosign sign ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }} 100 | -------------------------------------------------------------------------------- /LsifDotnet/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Builder; 3 | using System.CommandLine.Hosting; 4 | using System.CommandLine.NamingConventionBinder; 5 | using System.CommandLine.Parsing; 6 | using System.Globalization; 7 | using System.IO; 8 | using System.Threading.Tasks; 9 | using LsifDotnet.Lsif; 10 | using LsifDotnet.Roslyn; 11 | using Microsoft.Build.Locator; 12 | using Microsoft.CodeAnalysis; 13 | using Microsoft.CodeAnalysis.MSBuild; 14 | using Microsoft.Extensions.Configuration; 15 | using Microsoft.Extensions.DependencyInjection; 16 | using Microsoft.Extensions.FileSystemGlobbing; 17 | using Microsoft.Extensions.Hosting; 18 | using Microsoft.Extensions.Logging; 19 | 20 | namespace LsifDotnet; 21 | 22 | class Program 23 | { 24 | static async Task Main(string[] args) 25 | { 26 | var builder = BuildCommandLine(); 27 | await builder.UseHost(_ => Host.CreateDefaultBuilder(), ConfigureHost) 28 | .UseDefaults() 29 | .Build() 30 | .InvokeAsync(args); 31 | } 32 | 33 | private static CommandLineBuilder BuildCommandLine() 34 | { 35 | var outputOption = new Option("--output", () => new FileInfo("dump.lsif"), 36 | "The lsif dump output file."); 37 | outputOption.AddAlias("-o"); 38 | 39 | var excludedFilesOption = new Option("--excludedFiles", "Absolute paths of the files to be excluded in the output,this can be either a source code file or a project file"); 40 | excludedFilesOption.AddAlias("-e"); 41 | 42 | var dotOption = new Option("--dot", "Dump graphviz dot file."); 43 | dotOption.AddAlias("-d"); 44 | 45 | var svgOption = new Option("--svg", "Dump graph svg file. (by quickchart.io/graphviz API)"); 46 | svgOption.AddAlias("-s"); 47 | 48 | var cultureOption = new Option("--culture", () => CultureInfo.CurrentUICulture, 49 | "The culture used to show hover info."); 50 | cultureOption.AddAlias("-c"); 51 | 52 | var quietOption = new Option("--quiet", "Be more quiet"); 53 | quietOption.AddAlias("-q"); 54 | 55 | var parallelismOption = new Option("--parallelism", 56 | "How many hover content generation tasks can be handled at the same time. When unset or set to 0, use legacy indexer, relative slow but need less memory."); 57 | parallelismOption.AddAlias("-p"); 58 | 59 | var startIndexOption = new Option("--index", 60 | "The start index of lsif items. Use a different file when concatenating other lsif files."); 61 | startIndexOption.AddAlias("-i"); 62 | 63 | var rootCommand = new RootCommand("An indexer that dumps lsif information from dotnet solution.") 64 | { 65 | new Argument("SolutionFile", 66 | "The solution to be indexed. Default is the only solution file in the current folder.") 67 | { Arity = ArgumentArity.ZeroOrOne }, 68 | outputOption, 69 | dotOption, 70 | svgOption, 71 | cultureOption, 72 | quietOption, 73 | parallelismOption, 74 | startIndexOption, 75 | excludedFilesOption 76 | }; 77 | 78 | rootCommand.Handler = CommandHandler.Create(IndexHandler.Process); 79 | 80 | return new CommandLineBuilder(rootCommand); 81 | } 82 | 83 | private static void ConfigureHost(IHostBuilder host) 84 | { 85 | host.ConfigureAppConfiguration(builder => builder.AddInMemoryCollection()); 86 | 87 | host.ConfigureLogging(builder => 88 | builder.AddFilter("Microsoft.Hosting.Lifetime", LogLevel.None)); 89 | 90 | host.ConfigureServices((_, collection) => collection.AddSingleton(_ => CreateWorkspace()) 91 | .AddSingleton() 92 | .AddSingleton() 93 | .AddTransient() 94 | .AddTransient() 95 | .AddTransient(services => (Workspace)services.GetRequiredService())); 96 | } 97 | 98 | private static MSBuildWorkspace CreateWorkspace() 99 | { 100 | MSBuildLocator.RegisterDefaults(); 101 | 102 | var workspace = MSBuildWorkspace.Create(); 103 | return workspace; 104 | } 105 | } -------------------------------------------------------------------------------- /LsifDotnet/Roslyn/CSharpIdentifierCollector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace LsifDotnet.Roslyn; 8 | 9 | public class CSharpIdentifierCollector : CSharpSyntaxWalker 10 | { 11 | public ILogger Logger { get; } 12 | 13 | public CSharpIdentifierCollector(ILogger logger) 14 | { 15 | Logger = logger; 16 | } 17 | 18 | public List IdentifierList { get; set; } = new(); 19 | 20 | public override void VisitClassDeclaration(ClassDeclarationSyntax node) 21 | { 22 | Logger.LogTrace($"Class {node.Identifier}"); 23 | IdentifierList.Add(node.Identifier); 24 | 25 | base.VisitClassDeclaration(node); 26 | } 27 | 28 | public override void VisitMethodDeclaration(MethodDeclarationSyntax node) 29 | { 30 | Logger.LogTrace($"Method {node.Identifier}"); 31 | IdentifierList.Add(node.Identifier); 32 | 33 | base.VisitMethodDeclaration(node); 34 | } 35 | 36 | public override void VisitVariableDeclarator(VariableDeclaratorSyntax node) 37 | { 38 | Logger.LogTrace($"Variable {node.Identifier}"); 39 | IdentifierList.Add(node.Identifier); 40 | 41 | base.VisitVariableDeclarator(node); 42 | } 43 | 44 | public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) 45 | { 46 | Logger.LogTrace($"Ctor {node.Identifier}"); 47 | IdentifierList.Add(node.Identifier); 48 | 49 | base.VisitConstructorDeclaration(node); 50 | } 51 | 52 | public override void VisitSingleVariableDesignation(SingleVariableDesignationSyntax node) 53 | { 54 | Logger.LogTrace($"Single Var Designation {node.Identifier}"); 55 | IdentifierList.Add(node.Identifier); 56 | 57 | base.VisitSingleVariableDesignation(node); 58 | } 59 | 60 | public override void VisitParameter(ParameterSyntax node) 61 | { 62 | Logger.LogTrace($"Parameter {node.Identifier}"); 63 | IdentifierList.Add(node.Identifier); 64 | base.VisitParameter(node); 65 | } 66 | 67 | public override void VisitStructDeclaration(StructDeclarationSyntax node) 68 | { 69 | Logger.LogTrace($"Struct {node.Identifier}"); 70 | IdentifierList.Add(node.Identifier); 71 | base.VisitStructDeclaration(node); 72 | } 73 | 74 | public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) 75 | { 76 | Logger.LogTrace($"Interface {node.Identifier}"); 77 | IdentifierList.Add(node.Identifier); 78 | base.VisitInterfaceDeclaration(node); 79 | } 80 | 81 | public override void VisitRecordDeclaration(RecordDeclarationSyntax node) 82 | { 83 | Logger.LogTrace($"Record {node.Identifier}"); 84 | IdentifierList.Add(node.Identifier); 85 | base.VisitRecordDeclaration(node); 86 | } 87 | 88 | public override void VisitEnumDeclaration(EnumDeclarationSyntax node) 89 | { 90 | Logger.LogTrace($"Enum {node.Identifier}"); 91 | IdentifierList.Add(node.Identifier); 92 | base.VisitEnumDeclaration(node); 93 | } 94 | 95 | public override void VisitDelegateDeclaration(DelegateDeclarationSyntax node) 96 | { 97 | Logger.LogTrace($"Delegate {node.Identifier}"); 98 | IdentifierList.Add(node.Identifier); 99 | base.VisitDelegateDeclaration(node); 100 | } 101 | 102 | public override void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) 103 | { 104 | Logger.LogTrace($"EnumMember {node.Identifier}"); 105 | IdentifierList.Add(node.Identifier); 106 | base.VisitEnumMemberDeclaration(node); 107 | } 108 | 109 | 110 | public override void VisitDestructorDeclaration(DestructorDeclarationSyntax node) 111 | { 112 | Logger.LogTrace($"Destructor {node.Identifier}"); 113 | IdentifierList.Add(node.Identifier); 114 | base.VisitDestructorDeclaration(node); 115 | } 116 | 117 | public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) 118 | { 119 | Logger.LogTrace($"Property {node.Identifier}"); 120 | IdentifierList.Add(node.Identifier); 121 | base.VisitPropertyDeclaration(node); 122 | } 123 | 124 | public override void VisitEventDeclaration(EventDeclarationSyntax node) 125 | { 126 | Logger.LogTrace($"Event {node.Identifier}"); 127 | IdentifierList.Add(node.Identifier); 128 | base.VisitEventDeclaration(node); 129 | } 130 | 131 | public override void VisitGenericName(GenericNameSyntax node) 132 | { 133 | Logger.LogTrace($"Generic {node.Identifier}"); 134 | IdentifierList.Add(node.Identifier); 135 | base.VisitGenericName(node); 136 | } 137 | 138 | public override void VisitIdentifierName(IdentifierNameSyntax node) 139 | { 140 | Logger.LogTrace($"Ident {node.Identifier}"); 141 | IdentifierList.Add(node.Identifier); 142 | base.VisitIdentifierName(node); 143 | } 144 | } -------------------------------------------------------------------------------- /LsifDotnet.Tests/KeywordTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using LsifDotnet.Lsif; 5 | using LsifDotnet.Roslyn; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | using Microsoft.CodeAnalysis.Text; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.FileSystemGlobbing; 11 | using Xunit; 12 | 13 | namespace LsifDotnet.Tests 14 | { 15 | public class KeywordTest 16 | { 17 | /// 18 | /// The adhoc workspace. 19 | /// 20 | private readonly AdhocWorkspace _adhocWorkspace; 21 | 22 | private readonly Project _project; 23 | 24 | private readonly ServiceProvider _serviceProvider; 25 | 26 | public KeywordTest() 27 | { 28 | _adhocWorkspace = new AdhocWorkspace(); 29 | _project = AddProject(nameof(KeywordTest)); 30 | _serviceProvider = new ServiceCollection() 31 | .AddLogging() 32 | .AddTransient() 33 | .AddSingleton() 34 | .AddTransient() 35 | .AddSingleton(_adhocWorkspace as Workspace) 36 | .BuildServiceProvider(); 37 | } 38 | 39 | private Project AddProject(string projectName) 40 | { 41 | return _adhocWorkspace.AddProject(ProjectInfo.Create(ProjectId.CreateNewId(), 42 | VersionStamp.Create(), 43 | projectName, projectName, "C#", "F:/dummyProjectPath.csproj", metadataReferences: new[] 44 | { 45 | MetadataReference.CreateFromFile(typeof(object).Assembly.Location) 46 | })); 47 | } 48 | 49 | [Fact] 50 | public async Task GlobalKeywordTest() 51 | { 52 | var code1 = SourceText.From( 53 | @"// 54 | using System; 55 | using System.Reflection; 56 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute("".NETCoreApp,Version=v6.0"", FrameworkDisplayName = """")]"); 57 | 58 | var code2 = SourceText.From(@"class Test : global::System.object {} // Adding extra global symbol"); 59 | 60 | var documentId1 = DocumentId.CreateNewId(_project.Id); 61 | var documentId2 = DocumentId.CreateNewId(AddProject("SecondProject").Id); 62 | 63 | TextLoader loader1 = TextLoader.From(TextAndVersion.Create(code1, VersionStamp.Create())); 64 | TextLoader loader2 = TextLoader.From(TextAndVersion.Create(code2, VersionStamp.Create())); 65 | 66 | _adhocWorkspace.AddDocument(DocumentInfo.Create(documentId1, $"{nameof(GlobalKeywordTest)}.1.cs", null, SourceCodeKind.Regular, loader1, "F:/dummy/dummyDocumentFilePath.cs")); 67 | _adhocWorkspace.AddDocument(DocumentInfo.Create(documentId2, $"{nameof(GlobalKeywordTest)}.2.cs", null, SourceCodeKind.Regular, loader2, "F:/dummy/dummyDocumentFilePath.cs")); 68 | 69 | var indexer = _serviceProvider.GetRequiredService(); 70 | 71 | await foreach (var item in indexer.EmitLsif()) 72 | { 73 | Assert.NotNull(item); 74 | } 75 | } 76 | 77 | /// 78 | /// See https://github.com/dotnet/roslyn/issues/58999 79 | /// 80 | /// 81 | [Fact] 82 | [System.Diagnostics.CodeAnalysis.SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1024")] 83 | public async Task RoslynAliasEqualBugTest() 84 | { 85 | { 86 | var code1 = SourceText.From( 87 | @"// 88 | using System; 89 | using System.Reflection; 90 | [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute("".NETCoreApp,Version=v6.0"", FrameworkDisplayName = """")]"); 91 | 92 | var code2 = SourceText.From(@"using global::System.IO; // Adding extra global symbol"); 93 | 94 | var doc1 = _adhocWorkspace.AddDocument(_project.Id, $"{nameof(GlobalKeywordTest)}.1.cs", 95 | code1); 96 | var doc2 = _adhocWorkspace.AddDocument(_project.Id, $"{nameof(GlobalKeywordTest)}.2.cs", 97 | code2); 98 | 99 | var globalSymbol1 = (await doc1.GetSemanticModelAsync())! 100 | .GetAliasInfo((await doc1.GetSyntaxRootAsync())! 101 | .DescendantNodes() 102 | .OfType() 103 | .First(ident => ident.Identifier.Text == "global")); 104 | var globalSymbol2 = (await doc2.GetSemanticModelAsync())! 105 | .GetAliasInfo((await doc2.GetSyntaxRootAsync())! 106 | .DescendantNodes() 107 | .OfType() 108 | .First(ident => ident.Identifier.Text == "global")); 109 | 110 | Assert.NotNull(globalSymbol1); 111 | Assert.NotNull(globalSymbol2); 112 | 113 | Assert.Throws(() => globalSymbol1!.Equals(globalSymbol2)); 114 | } 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /LsifDotnet.Benchmark/SelfEmit.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Globalization; 3 | using System.Threading.Tasks.Dataflow; 4 | using BenchmarkDotNet.Attributes; 5 | using BenchmarkDotNet.Engines; 6 | using LsifDotnet.Lsif; 7 | using LsifDotnet.Roslyn; 8 | using Microsoft.Build.Locator; 9 | using Microsoft.CodeAnalysis; 10 | using Microsoft.CodeAnalysis.MSBuild; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using Microsoft.Extensions.Hosting; 13 | using Microsoft.Extensions.Logging; 14 | using IHost = Microsoft.Extensions.Hosting.IHost; 15 | 16 | namespace LsifDotnet.Benchmark; 17 | 18 | [MemoryDiagnoser] 19 | [ThreadingDiagnoser] 20 | [SimpleJob(RunStrategy.Monitoring)] 21 | public class SelfEmit 22 | { 23 | private IHost? _host; 24 | 25 | public FileInfo SolutionFileInfo => new FileInfo(@"F:\Code\Science\LsifDotnet\LsifDotnet.sln"); 26 | 27 | [GlobalSetup] 28 | public void SetupMSBuild() 29 | { 30 | MSBuildLocator.RegisterDefaults(); 31 | } 32 | 33 | [IterationSetup] 34 | public void BuildHost() 35 | { 36 | _host = Host.CreateDefaultBuilder() 37 | .ConfigureLogging((_, builder) => builder.ClearProviders()) 38 | .ConfigureServices((_, collection) => collection.AddSingleton(_ => CreateWorkspace()) 39 | .AddSingleton() 40 | .AddTransient() 41 | .AddTransient() 42 | .AddTransient(services => (Workspace)services.GetRequiredService())) 43 | .Build(); 44 | } 45 | 46 | 47 | private static MSBuildWorkspace CreateWorkspace() 48 | { 49 | var workspace = MSBuildWorkspace.Create(); 50 | return workspace; 51 | } 52 | 53 | [Benchmark] 54 | public async Task AsyncEnumerable() 55 | { 56 | const string fileName = 57 | $@"F:\Code\Science\LsifDotnet\LsifDotnet.Benchmark\bin\Release\test\{nameof(AsyncEnumerable)}.lsif"; 58 | await IndexHandler.Process(_host!, SolutionFileInfo, new FileInfo(fileName), 59 | CultureInfo.CurrentUICulture, false, false, true, 4, 0, new string[] {}); 60 | } 61 | 62 | [Benchmark] 63 | [Arguments(1)] 64 | [Arguments(2)] 65 | [Arguments(4)] 66 | [Arguments(8)] 67 | public async Task DataFlow(int maxDegreeOfParallelism) 68 | { 69 | var indexer = _host!.Services.GetRequiredService(); 70 | var logger = _host.Services.GetRequiredService>(); 71 | 72 | var stopwatch = Stopwatch.StartNew(); 73 | var defaultCulture = CultureInfo.CurrentUICulture; 74 | CultureInfo.CurrentUICulture = CultureInfo.CurrentUICulture; 75 | 76 | var items = indexer.BuildLsifEmitGraph(maxDegreeOfParallelism); 77 | 78 | const string path = 79 | $@"F:\Code\Science\LsifDotnet\LsifDotnet.Benchmark\bin\Release\test\{nameof(DataFlow)}.lsif"; 80 | var writer = new StreamWriter(path); 81 | var count = 0; 82 | var saveLsifBlock = new ActionBlock(async item => 83 | { 84 | var json = item.ToJson(); 85 | await writer.WriteLineAsync(json); 86 | Interlocked.Increment(ref count); 87 | }); 88 | 89 | items.LinkTo(saveLsifBlock, new DataflowLinkOptions { PropagateCompletion = true }); 90 | 91 | var solution = await _host.Services.GetRequiredService() 92 | .OpenSolutionAsync(SolutionFileInfo.FullName); 93 | await items.SendAsync(solution); 94 | items.Complete(); 95 | 96 | await saveLsifBlock.Completion; 97 | await writer.DisposeAsync(); 98 | 99 | stopwatch.Stop(); 100 | logger.LogInformation("Totally emitted {count} items in {time}", indexer.EmittedItem, stopwatch.Elapsed); 101 | CultureInfo.CurrentUICulture = defaultCulture; 102 | 103 | Trace.Assert(count == indexer.EmittedItem); 104 | } 105 | 106 | 107 | [Benchmark] 108 | [Arguments(1)] 109 | [Arguments(2)] 110 | [Arguments(4)] 111 | [Arguments(8)] 112 | public async Task DataFlowWithAsyncIO(int maxDegreeOfParallelism) 113 | { 114 | var indexer = _host!.Services.GetRequiredService(); 115 | var logger = _host.Services.GetRequiredService>(); 116 | 117 | var stopwatch = Stopwatch.StartNew(); 118 | var defaultCulture = CultureInfo.CurrentUICulture; 119 | CultureInfo.CurrentUICulture = CultureInfo.CurrentUICulture; 120 | 121 | var items = indexer.BuildLsifEmitGraph(maxDegreeOfParallelism); 122 | 123 | const string path = 124 | $@"F:\Code\Science\LsifDotnet\LsifDotnet.Benchmark\bin\Release\test\{nameof(DataFlowWithAsyncIO)}.lsif"; 125 | var writer = File.Create(path); 126 | var count = 0; 127 | var saveLsifBlock = new ActionBlock(async item => 128 | { 129 | await item.ToJsonAsync(writer); 130 | writer.WriteByte((byte)'\n'); 131 | Interlocked.Increment(ref count); 132 | }); 133 | 134 | items.LinkTo(saveLsifBlock, new DataflowLinkOptions { PropagateCompletion = true }); 135 | 136 | var solution = await _host.Services.GetRequiredService() 137 | .OpenSolutionAsync(SolutionFileInfo.FullName); 138 | await items.SendAsync(solution); 139 | items.Complete(); 140 | 141 | await saveLsifBlock.Completion; 142 | await writer.DisposeAsync(); 143 | 144 | stopwatch.Stop(); 145 | logger.LogInformation("Totally emitted {count} items in {time}", indexer.EmittedItem, stopwatch.Elapsed); 146 | CultureInfo.CurrentUICulture = defaultCulture; 147 | 148 | Trace.Assert(count == indexer.EmittedItem); 149 | } 150 | } -------------------------------------------------------------------------------- /LsifDotnet/IndexHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Threading.Tasks.Dataflow; 11 | using LsifDotnet.Lsif; 12 | using Microsoft.CodeAnalysis.MSBuild; 13 | using Microsoft.Extensions.Configuration; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Microsoft.Extensions.FileSystemGlobbing; 16 | using Microsoft.Extensions.Hosting; 17 | using Microsoft.Extensions.Logging; 18 | 19 | namespace LsifDotnet; 20 | 21 | public class IndexHandler 22 | { 23 | public static async Task Process(IHost host, FileInfo? solutionFile, FileInfo output, CultureInfo culture, bool dot, 24 | bool svg, bool quiet, uint parallelism, uint index, string[] excludedFiles) 25 | { 26 | var logger = host.Services.GetRequiredService>(); 27 | ConfigLoggingLevel(host, quiet); 28 | PrintDescription(logger); 29 | 30 | 31 | var solutionFilePath = solutionFile?.FullName ?? FindSolutionFile(); 32 | 33 | await host.Services.GetRequiredService().OpenSolutionAsync(solutionFilePath); 34 | var matcher = host.Services.GetRequiredService(); 35 | matcher.AddIncludePatterns(excludedFiles); 36 | 37 | 38 | var defaultCulture = CultureInfo.CurrentUICulture; 39 | CultureInfo.CurrentUICulture = culture; 40 | 41 | if (parallelism is 0) 42 | await LegacyLsifIndex(host, logger, output, dot, svg); 43 | else 44 | await DataFlowLsifIndex(host, logger, output, dot, svg, parallelism, index); 45 | 46 | CultureInfo.CurrentUICulture = defaultCulture; 47 | } 48 | 49 | private static async Task DataFlowLsifIndex(IHost host, ILogger logger, FileInfo output, bool dot, 50 | bool svg, uint parallelism, uint initId) 51 | { 52 | logger.LogInformation($"Using {nameof(DataFlowLsifIndexer)}"); 53 | var indexer = host.Services.GetRequiredService(); 54 | var stopwatch = Stopwatch.StartNew(); 55 | 56 | var items = indexer.BuildLsifEmitGraph((int)parallelism, (int)initId); 57 | 58 | var writer = output.Create(); 59 | var count = 0; 60 | var saveLsifBlock = new ActionBlock(async item => 61 | { 62 | await item.ToJsonAsync(writer); 63 | writer.WriteByte((byte)'\n'); 64 | Interlocked.Increment(ref count); 65 | }); 66 | var completion = saveLsifBlock.Completion; 67 | 68 | var graphBuilder = new GraphBuilder(); 69 | if (dot || svg) 70 | { 71 | var graphBuildBlock = graphBuilder.BuildDataFlowBlock(); 72 | items.LinkTo(graphBuildBlock); 73 | completion = graphBuildBlock.Completion; 74 | } 75 | 76 | items.LinkTo(saveLsifBlock, new DataflowLinkOptions { PropagateCompletion = true }); 77 | 78 | var solution = host.Services.GetRequiredService().CurrentSolution; 79 | await items.SendAsync(solution); 80 | items.Complete(); 81 | 82 | await completion; 83 | await writer.DisposeAsync(); 84 | 85 | stopwatch.Stop(); 86 | logger.LogInformation("Totally emitted {count} items in {time}", indexer.EmittedItem, stopwatch.Elapsed); 87 | 88 | if (dot) await graphBuilder.SaveDotAsync(); 89 | if (svg) await graphBuilder.SaveSvgAsync(); 90 | } 91 | 92 | private static async Task LegacyLsifIndex(IHost host, ILogger logger, FileInfo output, bool dot, 93 | bool svg) 94 | { 95 | logger.LogInformation($"Using {nameof(LegacyLsifIndexer)}"); 96 | var indexer = host.Services.GetRequiredService(); 97 | 98 | var stopwatch = Stopwatch.StartNew(); 99 | 100 | var items = indexer.EmitLsif(); 101 | var graphBuilder = new GraphBuilder(); 102 | if (dot || svg) items = graphBuilder.RecordLsifItem(items); 103 | await SaveLsifDump(items, output.FullName); 104 | 105 | stopwatch.Stop(); 106 | logger.LogInformation("Totally emitted {count} items in {time}", indexer.EmittedItem, stopwatch.Elapsed); 107 | 108 | if (dot) await graphBuilder.SaveDotAsync(); 109 | if (svg) await graphBuilder.SaveSvgAsync(); 110 | } 111 | 112 | private static void ConfigLoggingLevel(IHost host, bool quiet) 113 | { 114 | if (!quiet) return; 115 | 116 | var root = host.Services.GetRequiredService(); 117 | root["Logging:LogLevel:Default"] = nameof(LogLevel.Error); 118 | ((IConfigurationRoot)root).Reload(); 119 | } 120 | 121 | private static void PrintDescription(ILogger logger) 122 | { 123 | var isDebug = false; 124 | #if DEBUG 125 | isDebug = true; 126 | #endif 127 | 128 | logger.LogInformation( 129 | "LsifDotnet - a language Server Indexing Format (LSIF) generator for dotnet. Version {version} {profile}", 130 | Assembly.GetExecutingAssembly().GetName().Version, 131 | isDebug ? "(Debug)" : string.Empty); 132 | } 133 | 134 | private static string FindSolutionFile() 135 | { 136 | var files = Directory.GetFiles(Directory.GetCurrentDirectory()).Where(file => 137 | string.Equals(Path.GetExtension(file), ".sln", StringComparison.OrdinalIgnoreCase)).ToList(); 138 | 139 | if (files.Count != 1) 140 | { 141 | throw new FileNotFoundException("Solution file not found or found more than one."); 142 | } 143 | 144 | return files.First(); 145 | } 146 | 147 | private static async Task SaveLsifDump(IAsyncEnumerable items, string dumpPath) 148 | { 149 | await using var writer = new StreamWriter(dumpPath); 150 | await foreach (var item in items) 151 | { 152 | var json = item.ToJson(); 153 | await writer.WriteLineAsync(json); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | /LsifDotnet/Properties/launchSettings.json 365 | -------------------------------------------------------------------------------- /LsifDotnet/Lsif/LsifItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Text; 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | using System.Threading.Tasks; 8 | using Microsoft.CodeAnalysis; 9 | using Microsoft.CodeAnalysis.Text; 10 | 11 | namespace LsifDotnet.Lsif; 12 | 13 | public abstract class LsifItem 14 | { 15 | public static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web) 16 | { 17 | Converters = 18 | { 19 | new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) 20 | }, 21 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull 22 | }; 23 | 24 | public const string MetaDataLabel = "metaData"; 25 | public const string ProjectLabel = "project"; 26 | public const string DocumentLabel = "document"; 27 | public const string RangeLabel = "range"; 28 | public const string ResultSetLabel = "resultSet"; 29 | public const string HoverResultLabel = "hoverResult"; 30 | public const string ReferenceResultLabel = "referenceResult"; 31 | public const string DefinitionResultLabel = "definitionResult"; 32 | public const string HoverRequestLabel = "textDocument/hover"; 33 | public const string ReferencesRequestLabel = "textDocument/references"; 34 | public const string DefinitionsRequestLabel = "textDocument/definition"; 35 | public const string NextLabel = "next"; 36 | public const string ItemLabel = "item"; 37 | public const string ContainsLabel = "contains"; 38 | public const string MonikerLabel = "moniker"; 39 | 40 | public const string CSharpLanguageId = "csharp"; 41 | 42 | protected LsifItem(int id, LsifItemType type, string label) 43 | { 44 | Id = id; 45 | Type = type; 46 | Label = label; 47 | } 48 | 49 | public int Id { get; set; } 50 | public LsifItemType Type { get; set; } 51 | public string Label { get; set; } 52 | 53 | public string ToJson() 54 | { 55 | return JsonSerializer.Serialize(this, SerializerOptions); 56 | } 57 | 58 | public Task ToJsonAsync(Stream stream) 59 | { 60 | return JsonSerializer.SerializeAsync(stream, this, SerializerOptions); 61 | } 62 | } 63 | 64 | class SingleEdge : LsifItem 65 | { 66 | public int InV { get; set; } 67 | public int OutV { get; set; } 68 | 69 | public SingleEdge(int id, string label, int outV, int inV) : base(id, LsifItemType.Edge, label) 70 | { 71 | OutV = outV; 72 | InV = inV; 73 | } 74 | 75 | public SingleEdge(int id, string label, LsifItem outV, LsifItem inV) : base(id, LsifItemType.Edge, label) 76 | { 77 | OutV = outV.Id; 78 | InV = inV.Id; 79 | } 80 | 81 | public static SingleEdge NextEdge(int id, LsifItem outV, LsifItem inV) 82 | { 83 | return new SingleEdge(id, NextLabel, outV, inV); 84 | } 85 | 86 | public static LsifItem NextEdge(int id, int outV, int inV) 87 | { 88 | return new SingleEdge(id, NextLabel, outV, inV); 89 | } 90 | 91 | public static SingleEdge HoverEdge(int id, LsifItem outV, LsifItem inV) 92 | { 93 | return new SingleEdge(id, HoverRequestLabel, outV, inV); 94 | } 95 | public static LsifItem HoverEdge(int id, int outV, int inV) 96 | { 97 | return new SingleEdge(id, HoverRequestLabel, outV, inV); 98 | } 99 | 100 | public static LsifItem ReferenceEdge(int id, int outV, int inV) 101 | { 102 | return new SingleEdge(id, ReferencesRequestLabel, outV, inV); 103 | } 104 | 105 | public static LsifItem DefinitionEdge(int id, int outV, int inV) 106 | { 107 | return new SingleEdge(id, DefinitionsRequestLabel, outV, inV); 108 | } 109 | 110 | public static LsifItem MonikerEdge(int id, int outV, int inV) 111 | { 112 | return new SingleEdge(id, MonikerLabel, outV, inV); 113 | } 114 | } 115 | 116 | internal class MultipleEdge : LsifItem 117 | { 118 | protected MultipleEdge(int id, int outV, List inVs, string label) : base(id, LsifItemType.Edge, label) 119 | { 120 | OutV = outV; 121 | InVs = inVs; 122 | } 123 | 124 | public int OutV { get; set; } 125 | public List InVs { get; set; } 126 | 127 | public static MultipleEdge ContainsEdge(int id, int outV, List inVs) 128 | { 129 | return new MultipleEdge(id, outV, inVs, ContainsLabel); 130 | } 131 | } 132 | 133 | class ItemEdge : MultipleEdge 134 | { 135 | public const string ReferencesProperty = "references"; 136 | public const string DefinitionsProperty = "definitions"; 137 | public int Document { get; set; } 138 | public string? Property { get; set; } 139 | 140 | public ItemEdge(int id, int outV, List inVs, int document, string? property = default) 141 | : base(id, outV, inVs, ItemLabel) 142 | { 143 | Document = document; 144 | Property = property; 145 | } 146 | 147 | public static ItemEdge ReferenceItemEdge(int id, int outV, List inVs, int documentId) 148 | { 149 | return new ItemEdge(id, outV, inVs, documentId, ReferencesProperty); 150 | } 151 | 152 | public static ItemEdge DefinitionItemEdge(int id, int outV, List inVs, int documentId) 153 | { 154 | return new ItemEdge(id, outV, inVs, documentId, DefinitionsProperty); 155 | } 156 | } 157 | 158 | class SimpleVertex : LsifItem 159 | { 160 | public SimpleVertex(int id, string label) : base(id, LsifItemType.Vertex, label) 161 | { 162 | } 163 | 164 | public static SimpleVertex ResultSet(int id) 165 | { 166 | return new SimpleVertex(id, ResultSetLabel); 167 | } 168 | 169 | public static SimpleVertex ReferenceResult(int id) 170 | { 171 | return new SimpleVertex(id, ReferenceResultLabel); 172 | } 173 | 174 | public static SimpleVertex DefinitionResult(int id) 175 | { 176 | return new SimpleVertex(id, DefinitionResultLabel); 177 | } 178 | } 179 | 180 | class MetaDataVertex : LsifItem 181 | { 182 | public const string LsifVersion = "0.4.0"; 183 | 184 | public static readonly ToolInfoRecord LsifDotnetToolInfo = 185 | new(Assembly.GetExecutingAssembly().GetName().Name!, 186 | Assembly.GetExecutingAssembly().GetName().Version?.ToString()); 187 | 188 | public string Version { get; set; } 189 | public string PositionEncoding => Encoding.Unicode.WebName; 190 | public string ProjectRoot { get; set; } 191 | public ToolInfoRecord ToolInfo { get; set; } 192 | 193 | internal readonly record struct ToolInfoRecord(string Name, string? Version = default, string[]? Args = default); 194 | 195 | public MetaDataVertex(int id, string projectRoot, string version = LsifVersion) 196 | : this(id, projectRoot, LsifDotnetToolInfo, version) 197 | { 198 | } 199 | 200 | public MetaDataVertex(int id, string projectRoot, ToolInfoRecord toolInfo, string version = LsifVersion) 201 | : base(id, LsifItemType.Vertex, MetaDataLabel) 202 | { 203 | ProjectRoot = projectRoot; 204 | ToolInfo = toolInfo; 205 | Version = version; 206 | } 207 | } 208 | 209 | class ProjectVertex : LsifItem 210 | { 211 | public string Resource { get; set; } 212 | public string Kind { get; set; } 213 | public string Name { get; set; } 214 | 215 | public ProjectVertex(int id, string uri, string name, string languageId = CSharpLanguageId) 216 | : base(id, LsifItemType.Vertex, ProjectLabel) 217 | { 218 | Resource = uri; 219 | Name = name; 220 | Kind = languageId; 221 | } 222 | } 223 | 224 | class DocumentVertex : LsifItem 225 | { 226 | public string Uri { get; set; } 227 | public string LanguageId { get; set; } 228 | 229 | public DocumentVertex(int id, string uri, string languageId = CSharpLanguageId) 230 | : base(id, LsifItemType.Vertex, DocumentLabel) 231 | { 232 | Uri = uri; 233 | LanguageId = languageId; 234 | } 235 | } 236 | 237 | class HoverResultVertex : LsifItem 238 | { 239 | public record HoverResult(List Contents); 240 | 241 | public HoverResult Result { get; set; } 242 | 243 | public HoverResultVertex(int id, List contents) : base(id, LsifItemType.Vertex, HoverResultLabel) 244 | { 245 | Result = new HoverResult(contents); 246 | } 247 | } 248 | 249 | class RangeVertex : LsifItem 250 | { 251 | public LinePosition Start { get; set; } 252 | public LinePosition End { get; set; } 253 | 254 | public RangeVertex(int id, LinePosition start, LinePosition end) : base(id, LsifItemType.Vertex, RangeLabel) 255 | { 256 | Start = start; 257 | End = end; 258 | } 259 | 260 | public RangeVertex(int id, FileLinePositionSpan linePositionSpan) 261 | : this(id, linePositionSpan.StartLinePosition, linePositionSpan.EndLinePosition) 262 | { 263 | } 264 | } 265 | 266 | class MonikerVertex : LsifItem 267 | { 268 | public MonikerKind Kind { get; set; } 269 | public string Scheme { get; set; } 270 | public string Identifier { get; set; } 271 | 272 | public MonikerVertex(int id, MonikerKind kind, string scheme, string identifier) : base(id, LsifItemType.Vertex, MonikerLabel) 273 | { 274 | Kind = kind; 275 | Scheme = scheme; 276 | Identifier = identifier; 277 | } 278 | } 279 | 280 | public enum MonikerKind 281 | { 282 | Export, 283 | Import 284 | } 285 | 286 | public enum LsifItemType 287 | { 288 | Vertex, 289 | Edge, 290 | } -------------------------------------------------------------------------------- /LsifDotnet/MarkdownHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | using Microsoft.CodeAnalysis; 7 | 8 | namespace LsifDotnet; 9 | 10 | public static class MarkdownHelper 11 | { 12 | private static readonly Regex EscapeRegex = new(@"([\\`\*_\{\}\[\]\(\)#+\-\.!])", RegexOptions.Compiled); 13 | 14 | public static string? Escape(string? markdown) 15 | { 16 | return markdown == null ? null : EscapeRegex.Replace(markdown, @"\$1"); 17 | } 18 | 19 | /// 20 | /// Indicates the start of a text container. The elements after through (but not 21 | /// including) the matching are rendered in a rectangular block which is positioned 22 | /// as an inline element relative to surrounding elements. The text of the element 23 | /// itself precedes the content of the container, and is typically a bullet or number header for an item in a 24 | /// list. 25 | /// 26 | private const string ContainerStart = nameof(ContainerStart); 27 | 28 | /// 29 | /// Indicates the end of a text container. See . 30 | /// 31 | private const string ContainerEnd = nameof(ContainerEnd); 32 | 33 | public static string TaggedTextToMarkdown( 34 | ImmutableArray taggedParts, 35 | MarkdownFormat markdownFormat) 36 | { 37 | var isInCodeBlock = false; 38 | var brokeLine = true; 39 | var afterFirstLine = false; 40 | var stringBuilder = new StringBuilder(); 41 | 42 | if (markdownFormat == MarkdownFormat.Italicize) 43 | { 44 | stringBuilder.Append("_"); 45 | } 46 | 47 | for (var i = 0; i < taggedParts.Length; i++) 48 | { 49 | var current = taggedParts[i]; 50 | 51 | if (brokeLine && markdownFormat != MarkdownFormat.Italicize) 52 | { 53 | Debug.Assert(!isInCodeBlock); 54 | brokeLine = false; 55 | var canFormatAsBlock = (afterFirstLine, markdownFormat) switch 56 | { 57 | (false, MarkdownFormat.FirstLineAsCSharp) => true, 58 | (true, MarkdownFormat.FirstLineDefaultRestCSharp) => true, 59 | (_, MarkdownFormat.AllTextAsCSharp) => true, 60 | _ => false 61 | }; 62 | 63 | if (!canFormatAsBlock) 64 | { 65 | // If we're on a new line and there are no text parts in the upcoming line, then we 66 | // can format the whole line as C# code instead of plaintext. Otherwise, we need to 67 | // intermix, and can only use simple ` code fences 68 | for (var j = i; j < taggedParts.Length; j++) 69 | { 70 | switch (taggedParts[j].Tag) 71 | { 72 | case TextTags.Text: 73 | canFormatAsBlock = false; 74 | goto endOfLineOrTextFound; 75 | 76 | case ContainerStart: 77 | case ContainerEnd: 78 | case TextTags.LineBreak: 79 | goto endOfLineOrTextFound; 80 | 81 | default: 82 | // If the block is just newlines, then we don't want to format that as 83 | // C# code. So, we default to false, set it to true if there's actually 84 | // content on the line, then set to false again if Text content is 85 | // encountered. 86 | canFormatAsBlock = true; 87 | continue; 88 | } 89 | } 90 | } 91 | else 92 | { 93 | // If it's just a newline, we're going to default to standard handling which will 94 | // skip the newline. 95 | canFormatAsBlock = !IndexIsTag(i, ContainerStart, ContainerEnd, TextTags.LineBreak); 96 | } 97 | 98 | endOfLineOrTextFound: 99 | if (canFormatAsBlock) 100 | { 101 | afterFirstLine = true; 102 | stringBuilder.Append("```csharp"); 103 | stringBuilder.AppendLine(); 104 | for (; i < taggedParts.Length; i++) 105 | { 106 | current = taggedParts[i]; 107 | if (current.Tag is ContainerStart or ContainerEnd or TextTags.LineBreak) 108 | { 109 | stringBuilder.AppendLine(); 110 | 111 | if (markdownFormat != MarkdownFormat.AllTextAsCSharp 112 | && markdownFormat != MarkdownFormat.FirstLineDefaultRestCSharp) 113 | { 114 | // End the code block 115 | stringBuilder.Append("```"); 116 | 117 | // We know we're at a line break of some kind, but it could be 118 | // a container start, so let the standard handling take care of it. 119 | goto standardHandling; 120 | } 121 | } 122 | else 123 | { 124 | stringBuilder.Append(current.Text); 125 | } 126 | } 127 | 128 | // If we're here, that means that the last part has been reached, so just 129 | // return. 130 | Debug.Assert(i == taggedParts.Length); 131 | stringBuilder.AppendLine(); 132 | stringBuilder.Append("```"); 133 | return stringBuilder.ToString(); 134 | } 135 | } 136 | 137 | standardHandling: 138 | switch (current.Tag) 139 | { 140 | case TextTags.Text when !isInCodeBlock: 141 | AddText(current.Text); 142 | break; 143 | 144 | case TextTags.Text: 145 | EndBlock(); 146 | AddText(current.Text); 147 | break; 148 | 149 | case TextTags.Space when isInCodeBlock: 150 | if (IndexIsTag(i + 1, TextTags.Text)) 151 | { 152 | EndBlock(); 153 | } 154 | 155 | AddText(current.Text); 156 | break; 157 | 158 | case TextTags.Space: 159 | case TextTags.Punctuation: 160 | AddText(current.Text); 161 | break; 162 | 163 | case ContainerStart: 164 | AddNewline(); 165 | AddText(current.Text); 166 | break; 167 | 168 | case ContainerEnd: 169 | AddNewline(); 170 | break; 171 | 172 | case TextTags.LineBreak: 173 | if (stringBuilder.Length != 0 && !IndexIsTag(i + 1, ContainerStart, ContainerEnd) && 174 | i + 1 != taggedParts.Length) 175 | { 176 | AddNewline(); 177 | } 178 | 179 | break; 180 | 181 | default: 182 | if (!isInCodeBlock) 183 | { 184 | isInCodeBlock = true; 185 | stringBuilder.Append('`'); 186 | } 187 | 188 | stringBuilder.Append(current.Text); 189 | brokeLine = false; 190 | break; 191 | } 192 | } 193 | 194 | if (isInCodeBlock) 195 | { 196 | EndBlock(); 197 | } 198 | 199 | if (!brokeLine && markdownFormat == MarkdownFormat.Italicize) 200 | { 201 | stringBuilder.Append("_"); 202 | } 203 | 204 | return stringBuilder.ToString(); 205 | 206 | void AddText(string? text) 207 | { 208 | brokeLine = false; 209 | afterFirstLine = true; 210 | if (!isInCodeBlock) 211 | { 212 | text = Escape(text); 213 | } 214 | 215 | stringBuilder.Append(text); 216 | } 217 | 218 | void AddNewline() 219 | { 220 | if (isInCodeBlock) 221 | { 222 | EndBlock(); 223 | } 224 | 225 | if (markdownFormat == MarkdownFormat.Italicize) 226 | { 227 | stringBuilder.Append("_"); 228 | } 229 | 230 | // Markdown needs 2 linebreaks to make a new paragraph 231 | stringBuilder.AppendLine(); 232 | stringBuilder.AppendLine(); 233 | brokeLine = true; 234 | 235 | if (markdownFormat == MarkdownFormat.Italicize) 236 | { 237 | stringBuilder.Append("_"); 238 | } 239 | } 240 | 241 | void EndBlock() 242 | { 243 | stringBuilder.Append('`'); 244 | isInCodeBlock = false; 245 | } 246 | 247 | bool IndexIsTag(int i, params string[] tags) 248 | => i < taggedParts.Length && tags.Contains(taggedParts[i].Tag); 249 | } 250 | } 251 | 252 | public enum MarkdownFormat 253 | { 254 | /// 255 | /// Only format entire lines as C# code if there is no standard text on the line 256 | /// 257 | Default, 258 | 259 | /// 260 | /// Italicize the section 261 | /// 262 | Italicize, 263 | 264 | /// 265 | /// Format the first line as C#, unconditionally 266 | /// 267 | FirstLineAsCSharp, 268 | 269 | /// 270 | /// Format the first line as default text, and format the rest of the lines as C#, unconditionally 271 | /// 272 | FirstLineDefaultRestCSharp, 273 | 274 | /// 275 | /// Format the entire set of text as C#, unconditionally 276 | /// 277 | AllTextAsCSharp 278 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LsifDotnet/Lsif/LegacyLsifIndexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using LsifDotnet.Roslyn; 9 | using Microsoft.CodeAnalysis; 10 | using Microsoft.CodeAnalysis.FindSymbols; 11 | using Microsoft.CodeAnalysis.QuickInfo; 12 | using Microsoft.Extensions.FileSystemGlobbing; 13 | using Microsoft.Extensions.Logging; 14 | 15 | namespace LsifDotnet.Lsif; 16 | 17 | /// 18 | /// The indexer to emit Lsif items from a roslyn workspace 19 | /// 20 | public class LegacyLsifIndexer 21 | { 22 | private int _emittedItem; 23 | 24 | public LegacyLsifIndexer(Workspace workspace, IdentifierCollectorFactory identifierCollectorFactory, 25 | ILogger logger, Matcher matcher) 26 | { 27 | Workspace = workspace; 28 | IdentifierCollectorFactory = identifierCollectorFactory; 29 | Logger = logger; 30 | Matcher = matcher; 31 | } 32 | 33 | public Workspace Workspace { get; } 34 | public IdentifierCollectorFactory IdentifierCollectorFactory { get; } 35 | 36 | protected ILogger Logger { get; } 37 | 38 | protected Matcher Matcher { get; } 39 | 40 | public int EmittedItem => _emittedItem; 41 | 42 | [System.Diagnostics.CodeAnalysis.SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1024")] 43 | protected Dictionary VisitedSymbols { get; } = 44 | new(SymbolEqualityComparer.Default); 45 | 46 | /// 47 | /// The symbol cache representing a ResultSet shared with related 48 | /// 49 | /// The ResultSet's Id 50 | /// The DefinitionResult's Id 51 | /// The ReferenceResult's Id 52 | /// All s referring the same symbol 53 | protected record CachedSymbolResult(int ResultSetId, int? DefinitionResultId, int? ReferenceResultId, 54 | List? ReferenceVs) 55 | { 56 | /// 57 | /// The ReferenceResult's Id 58 | /// 59 | public int? ReferenceResultId { get; set; } = ReferenceResultId; 60 | /// 61 | /// The DefinitionResult's Id 62 | /// 63 | public int? DefinitionResultId { get; set; } = DefinitionResultId; 64 | } 65 | 66 | /// 67 | /// A s referring some symbol 68 | /// 69 | /// The Id of the 70 | /// true if this ref also the definition 71 | protected readonly record struct SymbolRef(int RangeId, bool IsDefinition); 72 | 73 | /// 74 | /// Emit Lsif items as 75 | /// 76 | /// 77 | public async IAsyncEnumerable EmitLsif() 78 | { 79 | var solution = Workspace.CurrentSolution; 80 | Logger.LogInformation("Emitting solution {solution}", solution.FilePath); 81 | 82 | yield return new MetaDataVertex(NextId(), ToAbsoluteUri(Path.GetDirectoryName(solution.FilePath))); 83 | 84 | foreach (var project in solution.Projects) 85 | { 86 | if (Matcher.Match(project.FilePath).HasMatches) 87 | { 88 | Logger.LogInformation("project {project} ignored", project.FilePath); 89 | continue; 90 | } 91 | Logger.LogInformation("Emitting {language} project {project}", project.Language, project.FilePath); 92 | 93 | var projectId = NextId(); 94 | var documents = new List(); 95 | yield return new ProjectVertex(projectId, ToAbsoluteUri(project.FilePath), project.Name); 96 | 97 | if (project.Language != "C#") 98 | { 99 | Logger.LogWarning($"Currently {project.Language} not supported"); 100 | continue; 101 | } 102 | 103 | foreach (var document in project.Documents) 104 | { 105 | if (Matcher.Match(document.FilePath).HasMatches) 106 | { 107 | Logger.LogInformation("Regular document {FilePath} ignored", document.FilePath); 108 | continue; 109 | } 110 | var documentId = NextId(); 111 | documents.Add(documentId); 112 | await foreach (var item in EmitDocument(documentId, document)) yield return item; 113 | } 114 | 115 | yield return MultipleEdge.ContainsEdge(NextId(), projectId, documents); 116 | } 117 | } 118 | 119 | private async IAsyncEnumerable EmitDocument(int documentId, Document document) 120 | { 121 | var previousEmittedItem = EmittedItem; 122 | Logger.LogInformation("Emitting {language} document {project}", document.SourceCodeKind, document.FilePath); 123 | var ranges = new List(); 124 | yield return new DocumentVertex(documentId, ToAbsoluteUri(document.FilePath)); 125 | 126 | var quickInfoService = QuickInfoService.GetService(document); 127 | Debug.Assert(quickInfoService != null, nameof(quickInfoService) + " != null"); 128 | 129 | var identifierCollector = IdentifierCollectorFactory.CreateInstance(); 130 | identifierCollector.Visit(await document.GetSyntaxRootAsync()); 131 | 132 | foreach (var token in identifierCollector.IdentifierList) 133 | { 134 | var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, token.SpanStart); 135 | var location = token.GetLocation(); 136 | var linePositionSpan = location.GetMappedLineSpan(); 137 | 138 | if (!location.IsInSource) 139 | { 140 | Logger.LogWarning($"Skipped not-in-source token {token.Value}"); 141 | continue; 142 | } 143 | 144 | if (SkipSymbol(symbol, token)) continue; 145 | 146 | var rangeVertex = new RangeVertex(NextId(), linePositionSpan); 147 | ranges.Add(rangeVertex.Id); 148 | yield return rangeVertex; 149 | 150 | var isDefinition = symbol.Locations.Any(defLocation => defLocation.Equals(location)); 151 | if (VisitedSymbols.TryGetValue(symbol, out var cachedSymbolResult)) 152 | { 153 | // Connect existing result set 154 | cachedSymbolResult.ReferenceVs?.Add(new SymbolRef(rangeVertex.Id, isDefinition)); 155 | yield return SingleEdge.NextEdge(NextId(), rangeVertex.Id, cachedSymbolResult.ResultSetId); 156 | continue; 157 | } 158 | 159 | var resultSetVertex = SimpleVertex.ResultSet(NextId()); 160 | yield return resultSetVertex; 161 | yield return SingleEdge.NextEdge(NextId(), rangeVertex, resultSetVertex); 162 | 163 | // Get hover info 164 | var contents = await GenerateHoverContent(quickInfoService, document, token); 165 | var hoverResultVertex = new HoverResultVertex(NextId(), contents); 166 | yield return hoverResultVertex; 167 | yield return SingleEdge.HoverEdge(NextId(), resultSetVertex, hoverResultVertex); 168 | 169 | var shouldImport = ShouldImport(symbol); 170 | if (shouldImport) 171 | { 172 | // Emit import info 173 | foreach (var item in EmitImportSymbol(symbol, resultSetVertex)) yield return item; 174 | } 175 | else if (ShouldExport(symbol)) 176 | { 177 | // Emit export info 178 | foreach (var item in EmitExportSymbol(symbol, resultSetVertex)) yield return item; 179 | } 180 | 181 | var referenceVs = new List { new(rangeVertex.Id, isDefinition) }; 182 | VisitedSymbols.Add(symbol, new CachedSymbolResult( 183 | resultSetVertex.Id, 184 | null, 185 | null, 186 | referenceVs)); 187 | } 188 | 189 | yield return MultipleEdge.ContainsEdge(NextId(), documentId, ranges); 190 | 191 | foreach (var lsifItem in EmitReferences(documentId)) yield return lsifItem; 192 | 193 | Logger.LogInformation("Emitted {count} Lsif item(s) in {document}", EmittedItem - previousEmittedItem, document.FilePath); 194 | } 195 | 196 | private bool SkipSymbol(ISymbol symbol, SyntaxToken token) 197 | { 198 | var linePositionSpan = token.GetLocation().GetMappedLineSpan(); 199 | switch (symbol) 200 | { 201 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse 202 | case null: 203 | { 204 | if (NotKnownIdentifier(token)) 205 | { 206 | Logger.LogWarning("Symbol not found {token.Value} at {linePositionSpan}", token.Value, 207 | linePositionSpan); 208 | } 209 | 210 | return true; 211 | } 212 | // Bug: AliasSymbol.Equals throw NullReferenceException when comparing two global symbols https://github.com/tcz717/LsifDotnet/issues/8 213 | // Remove this case when it is fixed 214 | case IAliasSymbol { Name: "global" }: 215 | Logger.LogTrace("Skipped one global symbol at {linePositionSpan}", linePositionSpan); 216 | return true; 217 | } 218 | 219 | return false; 220 | } 221 | 222 | private static string ToAbsoluteUri(string? path) 223 | { 224 | return path == null ? string.Empty : new Uri(path).AbsoluteUri; 225 | } 226 | 227 | private static bool NotKnownIdentifier(SyntaxToken token) 228 | { 229 | return token.Text != "nameof"; 230 | } 231 | 232 | 233 | /// 234 | /// Section kind for nullability analysis. 235 | /// 236 | /// Based on 237 | /// https://github.com/dotnet/roslyn/blob/7dc32a952e77c96c31cae6a2ba6d253a558fc7ff/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs 238 | /// These are internal tag values taken from 239 | /// https://github.com/dotnet/roslyn/blob/master/src/Features/Core/Portable/Common/TextTags.cs 240 | /// 241 | /// 242 | /// They're copied here so that we can ensure we render blocks correctly in the markdown 243 | /// https://github.com/dotnet/roslyn/issues/46254 tracks making these public 244 | /// 245 | /// 246 | internal const string NullabilityAnalysis = nameof(NullabilityAnalysis); 247 | 248 | private async Task> GenerateHoverContent(QuickInfoService quickInfoService, Document document, 249 | SyntaxToken token) 250 | { 251 | var quickInfo = await quickInfoService.GetQuickInfoAsync(document, token.SpanStart); 252 | 253 | if (quickInfo == null) 254 | { 255 | Logger.LogWarning("No quick info found for {token.Value} at {token.GetLocation()}", token.Value, 256 | token.GetLocation()); 257 | return new List(); 258 | } 259 | 260 | var contents = new List(); 261 | 262 | var description = quickInfo.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.Description); 263 | if (description != null) 264 | { 265 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(description.TaggedParts, MarkdownFormat.AllTextAsCSharp)); 266 | } 267 | 268 | var summary = quickInfo.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.DocumentationComments); 269 | if (summary != null) 270 | { 271 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(summary.TaggedParts, MarkdownFormat.Default)); 272 | } 273 | 274 | foreach (var section in quickInfo.Sections) 275 | { 276 | switch (section.Kind) 277 | { 278 | case QuickInfoSectionKinds.Description: 279 | case QuickInfoSectionKinds.DocumentationComments: 280 | continue; 281 | 282 | case QuickInfoSectionKinds.TypeParameters: 283 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, 284 | MarkdownFormat.AllTextAsCSharp)); 285 | break; 286 | 287 | case QuickInfoSectionKinds.AnonymousTypes: 288 | // The first line is "Anonymous Types:" 289 | // Then we want all anonymous types to be C# highlighted 290 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, 291 | MarkdownFormat.FirstLineDefaultRestCSharp)); 292 | break; 293 | 294 | case NullabilityAnalysis: 295 | // Italicize the nullable analysis for emphasis. 296 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, MarkdownFormat.Italicize)); 297 | break; 298 | 299 | default: 300 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, MarkdownFormat.Default)); 301 | break; 302 | } 303 | } 304 | 305 | return contents; 306 | } 307 | 308 | private IEnumerable EmitImportSymbol(ISymbol symbol, SimpleVertex resultSetVertex) 309 | { 310 | var monikerVertex = new MonikerVertex(NextId(), MonikerKind.Import, "csharp", 311 | symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); 312 | yield return monikerVertex; 313 | yield return SingleEdge.MonikerEdge(NextId(), resultSetVertex.Id, monikerVertex.Id); 314 | } 315 | 316 | private IEnumerable EmitExportSymbol(ISymbol symbol, SimpleVertex resultSetVertex) 317 | { 318 | // TODO: decide scheme name and identity format 319 | var monikerVertex = new MonikerVertex(NextId(), MonikerKind.Export, "csharp", 320 | symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); 321 | yield return monikerVertex; 322 | yield return SingleEdge.MonikerEdge(NextId(), resultSetVertex.Id, monikerVertex.Id); 323 | } 324 | 325 | private static bool ShouldImport(ISymbol symbol) 326 | { 327 | return symbol.Locations.Any(loc => loc.IsInMetadata); 328 | } 329 | 330 | private static bool ShouldExport(ISymbol symbol) 331 | { 332 | var isInSource = symbol.Locations.Any(loc => loc.IsInSource); 333 | return symbol.DeclaredAccessibility == Accessibility.Public && symbol.Kind != SymbolKind.Local && isInSource; 334 | } 335 | 336 | private IEnumerable EmitReferences(int documentId) 337 | { 338 | foreach (var cachedSymbolResult in VisitedSymbols.Values.Where(cachedSymbolResult => 339 | cachedSymbolResult.ReferenceVs?.Any() == true)) 340 | { 341 | Debug.Assert(cachedSymbolResult.ReferenceVs != null, "cachedSymbolResult.ReferenceVs != null"); 342 | 343 | if (cachedSymbolResult.ReferenceResultId == null) 344 | { 345 | var referenceResultVertex = SimpleVertex.ReferenceResult(NextId()); 346 | yield return referenceResultVertex; 347 | yield return SingleEdge.ReferenceEdge(NextId(), cachedSymbolResult.ResultSetId, 348 | referenceResultVertex.Id); 349 | cachedSymbolResult.ReferenceResultId = referenceResultVertex.Id; 350 | } 351 | 352 | var defVs = cachedSymbolResult.ReferenceVs.Where(refV => refV.IsDefinition) 353 | .Select(refV => refV.RangeId) 354 | .ToList(); 355 | if (defVs.Any()) 356 | { 357 | if (cachedSymbolResult.DefinitionResultId == null) 358 | { 359 | var definitionResultVertex = SimpleVertex.DefinitionResult(NextId()); 360 | yield return definitionResultVertex; 361 | yield return SingleEdge.DefinitionEdge(NextId(), cachedSymbolResult.ResultSetId, 362 | definitionResultVertex.Id); 363 | cachedSymbolResult.DefinitionResultId = definitionResultVertex.Id; 364 | } 365 | 366 | yield return new ItemEdge(NextId(), cachedSymbolResult.DefinitionResultId.Value, defVs, documentId); 367 | yield return ItemEdge.DefinitionItemEdge( 368 | NextId(), cachedSymbolResult.ReferenceResultId.Value, defVs, documentId); 369 | } 370 | 371 | var refVs = cachedSymbolResult.ReferenceVs.Where(refV => !refV.IsDefinition) 372 | .Select(refV => refV.RangeId) 373 | .ToList(); 374 | if (refVs.Any()) 375 | { 376 | yield return ItemEdge.ReferenceItemEdge( 377 | NextId(), cachedSymbolResult.ReferenceResultId.Value, refVs, documentId); 378 | } 379 | 380 | cachedSymbolResult.ReferenceVs.Clear(); 381 | } 382 | } 383 | 384 | private int NextId() 385 | { 386 | return Interlocked.Increment(ref _emittedItem); 387 | } 388 | } -------------------------------------------------------------------------------- /LsifDotnet/Lsif/DataFlowLsifIndexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Threading.Tasks.Dataflow; 10 | using LsifDotnet.Roslyn; 11 | using Microsoft.CodeAnalysis; 12 | using Microsoft.CodeAnalysis.FindSymbols; 13 | using Microsoft.CodeAnalysis.QuickInfo; 14 | using Microsoft.Extensions.FileSystemGlobbing; 15 | using Microsoft.Extensions.Logging; 16 | 17 | namespace LsifDotnet.Lsif; 18 | 19 | public class DataFlowLsifIndexer 20 | { 21 | private int _emittedItem; 22 | private int _initId; 23 | 24 | public DataFlowLsifIndexer(Workspace workspace, IdentifierCollectorFactory identifierCollectorFactory, 25 | ILogger logger, Matcher matcher) 26 | { 27 | Workspace = workspace; 28 | IdentifierCollectorFactory = identifierCollectorFactory; 29 | Logger = logger; 30 | Matcher = matcher; 31 | } 32 | 33 | public Workspace Workspace { get; } 34 | public IdentifierCollectorFactory IdentifierCollectorFactory { get; } 35 | protected ILogger Logger { get; } 36 | protected Matcher Matcher { get; } 37 | 38 | public int EmittedItem => _emittedItem - _initId; 39 | 40 | [System.Diagnostics.CodeAnalysis.SuppressMessage("MicrosoftCodeAnalysisCorrectness", "RS1024")] 41 | protected Dictionary VisitedSymbols { get; } = 42 | new(SymbolEqualityComparer.Default); 43 | 44 | /// 45 | /// The symbol cache representing a ResultSet shared with related 46 | /// 47 | /// The ResultSet's Id 48 | /// The DefinitionResult's Id 49 | /// The ReferenceResult's Id 50 | /// All s referring the same symbol 51 | protected record CachedSymbolResult(int ResultSetId, int? DefinitionResultId, int? ReferenceResultId, 52 | List? ReferenceVs) 53 | { 54 | /// 55 | /// The ReferenceResult's Id 56 | /// 57 | public int? ReferenceResultId { get; set; } = ReferenceResultId; 58 | 59 | /// 60 | /// The DefinitionResult's Id 61 | /// 62 | public int? DefinitionResultId { get; set; } = DefinitionResultId; 63 | } 64 | 65 | /// 66 | /// A s referring some symbol 67 | /// 68 | /// The Id of the 69 | /// true if this ref also the definition 70 | protected readonly record struct SymbolRef(int RangeId, bool IsDefinition); 71 | 72 | protected readonly record struct IndexedProject(int Id, Project Project); 73 | 74 | protected readonly record struct IndexedDocument(int Id, Document Document); 75 | 76 | protected readonly record struct HoverContentRequest(int ResultSetId, Document Document, int Position); 77 | 78 | 79 | public IPropagatorBlock BuildLsifEmitGraph(int maxDegreeOfParallelism = 4, int initId = 0) 80 | { 81 | _emittedItem = initId; 82 | _initId = initId; 83 | 84 | var solutionSource = new BufferBlock(); 85 | 86 | var lsifItemOutput = ReorderLsifItems(); 87 | 88 | var toProjects = new TransformManyBlock(async solution => 89 | { 90 | Logger.LogInformation("Emitting solution {solution}", solution.FilePath); 91 | await lsifItemOutput.SendAsync( 92 | new MetaDataVertex(NextId(), ToAbsoluteUri(Path.GetDirectoryName(solution.FilePath)))); 93 | 94 | return solution.Projects.Select(project => new IndexedProject(NextId(), project)); 95 | }); 96 | 97 | var toDocuments = new TransformManyBlock(async indexedProject => 98 | { 99 | var (projectId, project) = indexedProject; 100 | 101 | if (Matcher.Match(project.FilePath).HasMatches) 102 | { 103 | Logger.LogInformation("project {project} ignored", project.FilePath); 104 | return ImmutableArray.Empty; 105 | } 106 | Logger.LogInformation("Emitting {language} project {project}", project.Language, project.FilePath); 107 | 108 | await lsifItemOutput.SendAsync(new ProjectVertex(projectId, ToAbsoluteUri(project.FilePath), project.Name)); 109 | 110 | if (project.Language != "C#") 111 | { 112 | Logger.LogWarning($"Currently {project.Language} not supported"); 113 | return ImmutableArray.Empty; 114 | } 115 | 116 | var documents = new List(); 117 | 118 | foreach (var document in project.Documents) 119 | { 120 | if (Matcher.Match(document.FilePath).HasMatches) 121 | { 122 | Logger.LogInformation("Regular document {FilePath} ignored", document.FilePath); 123 | continue; 124 | } 125 | var documentId = NextId(); 126 | documents.Add(new IndexedDocument(documentId, document)); 127 | Trace.Assert( 128 | await lsifItemOutput.SendAsync(new DocumentVertex(documentId, ToAbsoluteUri(document.FilePath)))); 129 | } 130 | 131 | Trace.Assert(await lsifItemOutput.SendAsync(MultipleEdge.ContainsEdge(NextId(), projectId, 132 | documents.Select(doc => doc.Id).ToList()))); 133 | 134 | return documents; 135 | }); 136 | 137 | var hoverHandleBlock = 138 | TransformAsyncEnumerable(GenerateHoverContent, 139 | new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }); 140 | 141 | var toLsifItem = TransformAsyncEnumerable(EmitDocument(hoverHandleBlock)); 142 | 143 | var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; 144 | solutionSource.LinkTo(toProjects, linkOptions); 145 | toProjects.LinkTo(toDocuments, linkOptions); 146 | toDocuments.LinkTo(toLsifItem, linkOptions); 147 | toLsifItem.LinkTo(lsifItemOutput); 148 | hoverHandleBlock.LinkTo(lsifItemOutput); 149 | 150 | toLsifItem.Completion.ContinueWith(_ => hoverHandleBlock.Complete()); 151 | 152 | Task.WhenAll(toLsifItem.Completion, hoverHandleBlock.Completion).ContinueWith(_ => lsifItemOutput.Complete()); 153 | 154 | return DataflowBlock.Encapsulate(solutionSource, lsifItemOutput); 155 | } 156 | 157 | private Func> EmitDocument( 158 | ITargetBlock hoverHandleBlock) 159 | { 160 | return EmitDocumentInternal; 161 | 162 | async IAsyncEnumerable EmitDocumentInternal(IndexedDocument indexedDocument) 163 | { 164 | var previousEmittedItem = EmittedItem; 165 | var (documentId, document) = indexedDocument; 166 | Logger.LogInformation("Emitting {language} document {project}", document.SourceCodeKind, document.FilePath); 167 | var ranges = new List(); 168 | 169 | var identifierCollector = IdentifierCollectorFactory.CreateInstance(); 170 | identifierCollector.Visit(await document.GetSyntaxRootAsync()); 171 | 172 | foreach (var token in identifierCollector.IdentifierList) 173 | { 174 | var symbol = await SymbolFinder.FindSymbolAtPositionAsync(document, token.SpanStart); 175 | var location = token.GetLocation(); 176 | var linePositionSpan = location.GetMappedLineSpan(); 177 | 178 | if (!location.IsInSource) 179 | { 180 | Logger.LogWarning($"Skipped not-in-source token {token.Value}"); 181 | continue; 182 | } 183 | 184 | if (SkipSymbol(symbol, token)) continue; 185 | 186 | var rangeVertex = new RangeVertex(NextId(), linePositionSpan); 187 | ranges.Add(rangeVertex.Id); 188 | yield return rangeVertex; 189 | 190 | var isDefinition = symbol.Locations.Any(defLocation => defLocation.Equals(location)); 191 | if (VisitedSymbols.TryGetValue(symbol, out var cachedSymbolResult)) 192 | { 193 | // Connect existing result set 194 | cachedSymbolResult.ReferenceVs?.Add(new SymbolRef(rangeVertex.Id, isDefinition)); 195 | yield return SingleEdge.NextEdge(NextId(), rangeVertex.Id, cachedSymbolResult.ResultSetId); 196 | continue; 197 | } 198 | 199 | var resultSetVertex = SimpleVertex.ResultSet(NextId()); 200 | yield return resultSetVertex; 201 | yield return SingleEdge.NextEdge(NextId(), rangeVertex, resultSetVertex); 202 | 203 | // Request hover info 204 | await hoverHandleBlock.SendAsync(new HoverContentRequest(resultSetVertex.Id, document, 205 | token.SpanStart)); 206 | 207 | var shouldImport = ShouldImport(symbol); 208 | if (shouldImport) 209 | { 210 | // Emit import info 211 | foreach (var item in EmitImportSymbol(symbol, resultSetVertex)) yield return item; 212 | } 213 | else if (ShouldExport(symbol)) 214 | { 215 | // Emit export info 216 | foreach (var item in EmitExportSymbol(symbol, resultSetVertex)) yield return item; 217 | } 218 | 219 | var referenceVs = new List { new(rangeVertex.Id, isDefinition) }; 220 | VisitedSymbols.Add(symbol, new CachedSymbolResult(resultSetVertex.Id, null, null, referenceVs)); 221 | } 222 | 223 | yield return MultipleEdge.ContainsEdge(NextId(), documentId, ranges); 224 | 225 | foreach (var lsifItem in EmitReferences(documentId)) yield return lsifItem; 226 | 227 | Logger.LogInformation("Emitted {count} Lsif item(s) in {document}", EmittedItem - previousEmittedItem, 228 | document.FilePath); 229 | } 230 | } 231 | 232 | private IPropagatorBlock ReorderLsifItems() 233 | { 234 | int? lastId = null; 235 | var queue = new PriorityQueue(); 236 | var source = new BufferBlock(); 237 | var target = new ActionBlock(item => 238 | { 239 | if (!lastId.HasValue || item.Id == lastId + 1) 240 | { 241 | lastId = item.Id; 242 | source.Post(item); 243 | } 244 | else 245 | { 246 | queue.Enqueue(item, item.Id); 247 | } 248 | 249 | while (queue.TryPeek(out _, out var id) && id == lastId + 1) 250 | { 251 | lastId = id; 252 | source.Post(queue.Dequeue()); 253 | } 254 | }); 255 | target.Completion.ContinueWith(_ => 256 | { 257 | while (queue.TryDequeue(out var item, out var id)) 258 | { 259 | if (id != lastId + 1) 260 | { 261 | Logger.LogError("Lsif item {id} is out of order when complete, expect {expect}", id, lastId + 1); 262 | } 263 | lastId = id; 264 | source.Post(item); 265 | } 266 | source.Complete(); 267 | }); 268 | return DataflowBlock.Encapsulate(target, source); 269 | } 270 | 271 | /// 272 | /// Build a block that emits a single Lsif item for async enumerable of 273 | /// 274 | /// The transform parameter type 275 | /// The async output Type 276 | /// The async transform 277 | /// The options 278 | /// 279 | public static IPropagatorBlock TransformAsyncEnumerable(Func> transform, 280 | ExecutionDataflowBlockOptions? executionDataflowBlockOptions = null) 281 | { 282 | var source = new BufferBlock(); 283 | 284 | var target = new ActionBlock(async item => 285 | { 286 | await foreach (var result in transform(item)) 287 | { 288 | Trace.Assert(await source.SendAsync(result)); 289 | } 290 | }, executionDataflowBlockOptions ?? new ExecutionDataflowBlockOptions()); 291 | 292 | target.Completion.ContinueWith(_ => source.Complete()); 293 | return DataflowBlock.Encapsulate(target, source); 294 | } 295 | 296 | private async IAsyncEnumerable GenerateHoverContent(HoverContentRequest hoverContentRequest) 297 | { 298 | var (resultSetId, document, position) = hoverContentRequest; 299 | var quickInfoService = QuickInfoService.GetService(document); 300 | Debug.Assert(quickInfoService != null, nameof(quickInfoService) + " != null"); 301 | 302 | var contents = await GenerateHoverContent(quickInfoService, document, position); 303 | var hoverResultVertex = new HoverResultVertex(NextId(), contents); 304 | yield return hoverResultVertex; 305 | yield return SingleEdge.HoverEdge(NextId(), resultSetId, hoverResultVertex.Id); 306 | } 307 | 308 | private bool SkipSymbol(ISymbol symbol, SyntaxToken token) 309 | { 310 | var linePositionSpan = token.GetLocation().GetMappedLineSpan(); 311 | switch (symbol) 312 | { 313 | // ReSharper disable once ConditionIsAlwaysTrueOrFalse 314 | case null: 315 | { 316 | if (NotKnownIdentifier(token)) 317 | { 318 | Logger.LogWarning("Symbol not found {token.Value} at {linePositionSpan}", token.Value, 319 | linePositionSpan); 320 | } 321 | 322 | return true; 323 | } 324 | // Bug: AliasSymbol.Equals throw NullReferenceException when comparing two global symbols https://github.com/tcz717/LsifDotnet/issues/8 325 | // Remove this case when it is fixed 326 | case IAliasSymbol { Name: "global" }: 327 | Logger.LogTrace("Skipped one global symbol at {linePositionSpan}", linePositionSpan); 328 | return true; 329 | } 330 | 331 | return false; 332 | } 333 | 334 | private static string ToAbsoluteUri(string? path) 335 | { 336 | return path == null ? string.Empty : new Uri(path).AbsoluteUri; 337 | } 338 | 339 | private static bool NotKnownIdentifier(SyntaxToken token) 340 | { 341 | return token.Text != "nameof"; 342 | } 343 | 344 | 345 | /// 346 | /// Section kind for nullability analysis. 347 | /// 348 | /// Based on 349 | /// https://github.com/dotnet/roslyn/blob/7dc32a952e77c96c31cae6a2ba6d253a558fc7ff/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs 350 | /// These are internal tag values taken from 351 | /// https://github.com/dotnet/roslyn/blob/master/src/Features/Core/Portable/Common/TextTags.cs 352 | /// 353 | /// 354 | /// They're copied here so that we can ensure we render blocks correctly in the markdown 355 | /// https://github.com/dotnet/roslyn/issues/46254 tracks making these public 356 | /// 357 | /// 358 | internal const string NullabilityAnalysis = nameof(NullabilityAnalysis); 359 | 360 | private async Task> GenerateHoverContent(QuickInfoService quickInfoService, Document document, 361 | int position) 362 | { 363 | var quickInfo = await quickInfoService.GetQuickInfoAsync(document, position); 364 | if (quickInfo == null) 365 | { 366 | Logger.LogWarning("No quick info found at {position}", position); 367 | return new List(); 368 | } 369 | 370 | var contents = new List(); 371 | 372 | var description = quickInfo.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.Description); 373 | if (description != null) 374 | { 375 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(description.TaggedParts, MarkdownFormat.AllTextAsCSharp)); 376 | } 377 | 378 | var summary = quickInfo.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.DocumentationComments); 379 | if (summary != null) 380 | { 381 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(summary.TaggedParts, MarkdownFormat.Default)); 382 | } 383 | 384 | foreach (var section in quickInfo.Sections) 385 | { 386 | switch (section.Kind) 387 | { 388 | case QuickInfoSectionKinds.Description: 389 | case QuickInfoSectionKinds.DocumentationComments: 390 | continue; 391 | 392 | case QuickInfoSectionKinds.TypeParameters: 393 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, 394 | MarkdownFormat.AllTextAsCSharp)); 395 | break; 396 | 397 | case QuickInfoSectionKinds.AnonymousTypes: 398 | // The first line is "Anonymous Types:" 399 | // Then we want all anonymous types to be C# highlighted 400 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, 401 | MarkdownFormat.FirstLineDefaultRestCSharp)); 402 | break; 403 | 404 | case NullabilityAnalysis: 405 | // Italicize the nullable analysis for emphasis. 406 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, MarkdownFormat.Italicize)); 407 | break; 408 | 409 | default: 410 | contents.Add(MarkdownHelper.TaggedTextToMarkdown(section.TaggedParts, MarkdownFormat.Default)); 411 | break; 412 | } 413 | } 414 | 415 | return contents; 416 | } 417 | 418 | private IEnumerable EmitImportSymbol(ISymbol symbol, SimpleVertex resultSetVertex) 419 | { 420 | var monikerVertex = new MonikerVertex(NextId(), MonikerKind.Import, "csharp", 421 | symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); 422 | yield return monikerVertex; 423 | yield return SingleEdge.MonikerEdge(NextId(), resultSetVertex.Id, monikerVertex.Id); 424 | } 425 | 426 | private IEnumerable EmitExportSymbol(ISymbol symbol, SimpleVertex resultSetVertex) 427 | { 428 | // TODO: decide scheme name and identity format 429 | var monikerVertex = new MonikerVertex(NextId(), MonikerKind.Export, "csharp", 430 | symbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat)); 431 | yield return monikerVertex; 432 | yield return SingleEdge.MonikerEdge(NextId(), resultSetVertex.Id, monikerVertex.Id); 433 | } 434 | 435 | private static bool ShouldImport(ISymbol symbol) 436 | { 437 | return symbol.Locations.Any(loc => loc.IsInMetadata); 438 | } 439 | 440 | private static bool ShouldExport(ISymbol symbol) 441 | { 442 | var isInSource = symbol.Locations.Any(loc => loc.IsInSource); 443 | return symbol.DeclaredAccessibility == Accessibility.Public && symbol.Kind != SymbolKind.Local && isInSource; 444 | } 445 | 446 | private IEnumerable EmitReferences(int documentId) 447 | { 448 | foreach (var cachedSymbolResult in VisitedSymbols.Values.Where(cachedSymbolResult => 449 | cachedSymbolResult.ReferenceVs?.Any() == true)) 450 | { 451 | Debug.Assert(cachedSymbolResult.ReferenceVs != null, "cachedSymbolResult.ReferenceVs != null"); 452 | 453 | if (cachedSymbolResult.ReferenceResultId == null) 454 | { 455 | var referenceResultVertex = SimpleVertex.ReferenceResult(NextId()); 456 | yield return referenceResultVertex; 457 | yield return SingleEdge.ReferenceEdge(NextId(), cachedSymbolResult.ResultSetId, 458 | referenceResultVertex.Id); 459 | cachedSymbolResult.ReferenceResultId = referenceResultVertex.Id; 460 | } 461 | 462 | var defVs = cachedSymbolResult.ReferenceVs.Where(refV => refV.IsDefinition) 463 | .Select(refV => refV.RangeId) 464 | .ToList(); 465 | if (defVs.Any()) 466 | { 467 | if (cachedSymbolResult.DefinitionResultId == null) 468 | { 469 | var definitionResultVertex = SimpleVertex.DefinitionResult(NextId()); 470 | yield return definitionResultVertex; 471 | yield return SingleEdge.DefinitionEdge(NextId(), cachedSymbolResult.ResultSetId, 472 | definitionResultVertex.Id); 473 | cachedSymbolResult.DefinitionResultId = definitionResultVertex.Id; 474 | } 475 | 476 | yield return new ItemEdge(NextId(), cachedSymbolResult.DefinitionResultId.Value, defVs, documentId); 477 | yield return ItemEdge.DefinitionItemEdge( 478 | NextId(), cachedSymbolResult.ReferenceResultId.Value, defVs, documentId); 479 | } 480 | 481 | var refVs = cachedSymbolResult.ReferenceVs.Where(refV => !refV.IsDefinition) 482 | .Select(refV => refV.RangeId) 483 | .ToList(); 484 | if (refVs.Any()) 485 | { 486 | yield return ItemEdge.ReferenceItemEdge( 487 | NextId(), cachedSymbolResult.ReferenceResultId.Value, refVs, documentId); 488 | } 489 | 490 | cachedSymbolResult.ReferenceVs.Clear(); 491 | } 492 | } 493 | 494 | private int NextId() 495 | { 496 | return Interlocked.Increment(ref _emittedItem); 497 | } 498 | } --------------------------------------------------------------------------------