TrailRunner-MF
TrailRunner-MF

Reputation: 11

Cannot get SyntaxTree from Compilation object

I'm a beginner of roslyn, so I tried to start learning it by making a very simple console application, which is introduced in the famous tutorial site. (https://riptutorial.com/roslyn/example/16545/introspective-analysis-of-an-analyzer-in-csharp), and it didn't work well.

The Cosole Application I made is of .NET Framework (target Framework version is 4.7.2), and not of .NET Core nor .NET standard. I added the NuGet package Microsoft.CodeAnalysis, and Microsoft.CodeAnalysis.Workspaces.MSBuild, then wrote a simple code as I show below.

using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Linq;

namespace SimpleRoslynConsole
{
class Program
    {
        static void Main(string[] args)
        {
            // Declaring a variable with the current project file path.
            // *** You have to change this path to fit your development environment.
            const string projectPath =
                @"C:\Users\[MyName]\Source\Repos\RoslynTrialConsole01\RoslynTrialConsole01.csproj";
            var workspace = MSBuildWorkspace.Create();
            var project = workspace.OpenProjectAsync(projectPath).Result;
            // [**1]Getting the compilation.
            var compilation = project.GetCompilationAsync().Result;
            // [**2]As this is a simple single file program, the first syntax tree will be the current file.
            var syntaxTree = compilation.SyntaxTrees.FirstOrDefault();
            if (syntaxTree != null)
            {
                var rootSyntaxNode = syntaxTree.GetRootAsync().Result;
                var firstLocalVariablesDeclaration = rootSyntaxNode.DescendantNodesAndSelf()
                    .OfType<LocalDeclarationStatementSyntax>().First();
                var firstVariable = firstLocalVariablesDeclaration.Declaration.Variables.First();
                var variableInitializer = firstVariable.Initializer.Value.GetFirstToken().ValueText;
                Console.WriteLine(variableInitializer);
            }
            else
            {
                Console.WriteLine("Could not get SyntaxTrees from this projects.");
            }
            Console.WriteLine("Hit any key.");
            Console.ReadKey();
        }
    }
}

My problem is that, SyntaxTrees property of Compilation object returns null in [**2]mark. Naturally, following FirstOrDefault method returns null.

I've tried several other code. I found I could get SyntaxTree from CSharp code text, by using CSharpSyntaxTree.ParseText method. But I couldn't get any from source code, by the sequence of

var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
var compilation = project.GetCompilationAsync().Result;

What I'd like to know is if I miss something to get Syntax information from source code by using above process.

I'll appreciate someone give me a good advice.

Upvotes: 1

Views: 2643

Answers (3)

IgorZhurbenko
IgorZhurbenko

Reputation: 141

I think the issue is that .net framework projects have their source files paths within their .csproj. And opening project works right away. For .net core project you have no such information and, maybe, this is why Workspace instance doesn't know what to load and so loads nothing. At least specifying .cs files as added documents does the trick. Try to apply this:

static class ProjectExtensions
{
    public static Project AddDocuments(this Project project, IEnumerable<string> files)
    {
        foreach (string file in files)
        {
            project = project.AddDocument(file, File.ReadAllText(file)).Project;
        }
        return project;
    }

    private static IEnumerable<string> GetAllSourceFiles(string directoryPath)
    {
        var res = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);

        return res;
    }


    public static Project WithAllSourceFiles(this Project project)
    {
        string projectDirectory = Directory.GetParent(project.FilePath).FullName;
        var files = GetAllSourceFiles(projectDirectory);
        var newProject = project.AddDocuments(files);
        return newProject;
    }
}

Method WithAllsourceFiles will return you the project, compilation of which will in its turn have all syntax trees you would expect of it, as you would have in Visual Studio

Upvotes: 3

TrailRunner-MF
TrailRunner-MF

Reputation: 11

I searched various sample programs on the net and found the most reliable and safest method. The solution is to create a static method which returns SyntaxTrees in designated File as follow.

private static Compilation CreateTestCompilation()
{
    var found = false;
    var di = new DirectoryInfo(Environment.CurrentDirectory);
    var fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
    while ((fi == null) || (di.Parent == null))
    {
        di = new DirectoryInfo(di.Parent.FullName);
        fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
        if (fi != null)
        {
            found = true;
            break;
        }
    }
    if (!found)
    {
        return null;
    }
    var targetPath = di.FullName +  @"\Program.cs";
    var targetText = File.ReadAllText(targetPath);
    var targetTree =
                   CSharpSyntaxTree.ParseText(targetText)
                                   .WithFilePath(targetPath);
    var target2Path = di.FullName + @"\TypeInferenceRewriter.cs";
    var target2Text = File.ReadAllText(target2Path);
    var target2Tree =
                   CSharpSyntaxTree.ParseText(target2Text)
                                   .WithFilePath(target2Path);

    SyntaxTree[] sourceTrees = { programTree, target2Tree };

    MetadataReference mscorlib =
            MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
    MetadataReference codeAnalysis =
            MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
    MetadataReference csharpCodeAnalysis =
            MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);

    MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };

    return CSharpCompilation.Create("TransformationCS",
                                    sourceTrees,
                                    references,
                                    new CSharpCompilationOptions(
                                            OutputKind.ConsoleApplication));
}

And the caller program will be like this.

static void Main(string[] args)
{
    var test = CreateTestCompilation();
    if (test == null)
    {
        return;
    }

    foreach (SyntaxTree sourceTree in test.SyntaxTrees)
    {
        Console.WriteLine(souceTree.ToFullString());
    }
}

Of course, many improvements are needed to put it to practical use.

Upvotes: 0

Matt Warren
Matt Warren

Reputation: 2016

MsBuildWorkspace won't work correctly unless you have all the same redirects in your app's app.config file that msbuild.exe.config has in it. Without the redirects, it's probably failing to load the msbuild libraries. You need to find the msbuild.exe.config file that is on your system and copy the <assemblyBinding> elements related to Microsoft.Build assemblies into your app.config. Make sure you place them under the correct elements configuration/runtime.

Upvotes: 0

Related Questions