Boas Enkler
Boas Enkler

Reputation: 12557

Getting Class FullName (including namespace) from Roslyn ClassDeclarationSyntax

I've a ClassDeclarationSyntax from a syntax tree in roslyn. I read it like this:

var tree = SyntaxTree.ParseText(sourceCode);
var root = (CompilationUnitSyntax)tree.GetRoot();

var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();

The identifier only contains the name of the class but no information about the namespace, so the fullType Name is missing. Like "MyClass" but noch "Namespace1.MyClass"

what is the recommended way to get the namespace / FulltypeName of the Syntax?

Upvotes: 30

Views: 17591

Answers (11)

joshcomley
joshcomley

Reputation: 28808

Here is what I use:

public static class SyntaxNodeExtensions
{
    public static string? GetNamespace(this SyntaxNode syntaxNode)
    {
        return string.Join(".", syntaxNode
                .Ancestors()
                .OfType<BaseNamespaceDeclarationSyntax>()
                .Reverse()
                .Select(_ => _.Name)
            );
    }
}

And example usage:

void Main()
{
    string code = @"
    namespace NoBrackets;
    
    namespace A
    {
        namespace B
        {
            namespace NoBrackets2;
            
            namespace C
            {
                public class Blah() { }
            }
        }
    }
    ";
    var tree = CSharpSyntaxTree.ParseText(code);
    var root = tree.GetRoot();
    var cl = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
    var ns = cl.GetNamespace();
    Console.WriteLine(ns);
}

Output is:

NoBrackets.A.B.NoBrackets2.C

Upvotes: 1

Alex from Jitbit
Alex from Jitbit

Reputation: 60536

Build an ITypeSymbol for your class definition.

Then get its "full qualified name"

ClassDeclarationSyntax myclass; //your class here

var typeSymbol = context.Compilation.GetSemanticModel(myclass.SyntaxTree).GetDeclaredSymbol(myclass);

typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
//-------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^this

Upvotes: 11

schwartz
schwartz

Reputation: 475

Andrew Lock have made a awesome blog post regarding (incremental) source generators. He describe how you can find the namespace for a class syntax taking into account file scope namespaces, nested namespaces and no namespace at all.

Check it out https://andrewlock.net/creating-a-source-generator-part-5-finding-a-type-declarations-namespace-and-type-hierarchy/#finding-the-namespace-for-a-class-syntax

Upvotes: 0

Antao Almada
Antao Almada

Reputation: 455

Based on the answer by Ronald, I added support for: nested namespaces, structs and generics.

It uses the generics CLR naming convention to allow the output to be used as input of Compilation.GetTypeByMetadataName()

public static class TypeDeclarationSyntaxExtensions
{
    const char NESTED_CLASS_DELIMITER = '+';
    const char NAMESPACE_CLASS_DELIMITER = '.';
    const char TYPEPARAMETER_CLASS_DELIMITER = '`';

    public static string GetFullName(this TypeDeclarationSyntax source)
    {
        if (source is null)
            throw new ArgumentNullException(nameof(source));

        var namespaces = new LinkedList<BaseNamespaceDeclarationSyntax>();
        var types = new LinkedList<TypeDeclarationSyntax>();
        for (var parent = source.Parent; parent is object; parent = parent.Parent)
        {
            if (parent is BaseNamespaceDeclarationSyntax @namespace)
            {
                namespaces.AddFirst(@namespace);
            }
            else if (parent is TypeDeclarationSyntax type)
            {
                types.AddFirst(type);
            }
        }

        var result = new StringBuilder();
        for (var item = namespaces.First; item is object; item = item.Next)
        {
            result.Append(item.Value.Name).Append(NAMESPACE_CLASS_DELIMITER);
        }
        for (var item = types.First; item is object; item = item.Next)
        {
            var type = item.Value;
            AppendName(result, type);
            result.Append(NESTED_CLASS_DELIMITER);
        }
        AppendName(result, source);

        return result.ToString();
    }

    static void AppendName(StringBuilder builder, TypeDeclarationSyntax type)
    {
        builder.Append(type.Identifier.Text);
        var typeArguments = type.TypeParameterList?.ChildNodes()
            .Count(node => node is TypeParameterSyntax) ?? 0;
        if (typeArguments != 0)
            builder.Append(TYPEPARAMETER_CLASS_DELIMITER).Append(typeArguments);
    }
}

Upvotes: 3

JunQiang
JunQiang

Reputation: 1

This code could solve your problem.

public static string ClassFullName(this ClassDeclarationSyntax varClassDec)
{
    SyntaxNode tempCurCls = varClassDec;
    var tempFullName = new Stack<string>();
    
    do
    {
        if (tempCurCls.Kind() == SyntaxKind.ClassDeclaration)
        {
            tempFullName.Push(((ClassDeclarationSyntax)tempCurCls).Identifier.ToString());
        }
        else if (tempCurCls.Kind() == SyntaxKind.NamespaceDeclaration)
        {
            tempFullName.Push(((NamespaceDeclarationSyntax)tempCurCls).Name.ToString());
        }
        
        tempCurCls = tempCurCls.Parent;
    } while (tempCurCls != null);
    
    return string.Join(".", tempFullName);
}

Upvotes: 0

Arley P&#225;dua
Arley P&#225;dua

Reputation: 156

There's also an elegant way when using pattern matching + recursion:

// method with pattern matching
public static string GetNamespaceFrom(SyntaxNode s) =>
    s.Parent switch
    {
        NamespaceDeclarationSyntax namespaceDeclarationSyntax => namespaceDeclarationSyntax.Name.ToString(),
        null => string.Empty, // or whatever you want to do
        _ => GetNamespaceFrom(s.Parent)
    };

// somewhere call it passing the class declaration syntax:
string ns = GetNamespaceFrom(classDeclarationSyntax);

Upvotes: 6

Timon de Groot
Timon de Groot

Reputation: 8153

Yet another answer.. This utility class supports nested namespaces and nested classes as well.

public static class SyntaxNodeHelper
{
    public static string GetPrefix(SyntaxNode member)
    {
        if (member == null) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        SyntaxNode node = member;

        while(node.Parent != null) {
            node = node.Parent;

            if (node is NamespaceDeclarationSyntax) {
                var namespaceDeclaration = (NamespaceDeclarationSyntax) node;

                sb.Insert(0, ".");
                sb.Insert(0, namespaceDeclaration.Name.ToString());
            } else if (node is ClassDeclarationSyntax) {
                var classDeclaration = (ClassDeclarationSyntax) node;

                sb.Insert(0, ".");
                sb.Insert(0, classDeclaration.Identifier.ToString());
            }
        }

        return sb.ToString();
    }
}

Upvotes: 0

I know I am rather late in the game, but I stumbled upon one of given the answers which did not work in my case where I had to deal with nested classes. So therefore here is a method that will handle nested classes as well:

public static class ClassDeclarationSyntaxExtensions
{
    public const string NESTED_CLASS_DELIMITER = "+";
    public const string NAMESPACE_CLASS_DELIMITER = ".";

    public static string GetFullName(this ClassDeclarationSyntax source)
    {
        Contract.Requires(null != source);

        var items = new List<string>();
        var parent = source.Parent;
        while (parent.IsKind(SyntaxKind.ClassDeclaration))
        {
            var parentClass = parent as ClassDeclarationSyntax;
            Contract.Assert(null != parentClass);
            items.Add(parentClass.Identifier.Text);

            parent = parent.Parent;
        }

        var nameSpace = parent as NamespaceDeclarationSyntax;
        Contract.Assert(null != nameSpace);
        var sb = new StringBuilder().Append(nameSpace.Name).Append(NAMESPACE_CLASS_DELIMITER);
        items.Reverse();
        items.ForEach(i => { sb.Append(i).Append(NESTED_CLASS_DELIMITER); });
        sb.Append(source.Identifier.Text);

        var result = sb.ToString();
        return result;
    }
}

Upvotes: 2

Jordan
Jordan

Reputation: 5425

you can do this using the helper class I wrote:

NamespaceDeclarationSyntax namespaceDeclarationSyntax = null;
if (!SyntaxNodeHelper.TryGetParentSyntax(classDeclarationSyntax, out namespaceDeclarationSyntax))
{
    return; // or whatever you want to do in this scenario
}

var namespaceName = namespaceDeclarationSyntax.Name.ToString();
var fullClassName = namespaceName + "." + classDeclarationSyntax.Identifier.ToString();

and the helper:

static class SyntaxNodeHelper
{
    public static bool TryGetParentSyntax<T>(SyntaxNode syntaxNode, out T result) 
        where T : SyntaxNode
    {
        // set defaults
        result = null;

        if (syntaxNode == null)
        {
            return false;
        }

        try
        {
            syntaxNode = syntaxNode.Parent;

            if (syntaxNode == null)
            {
                return false;
            }

            if (syntaxNode.GetType() == typeof (T))
            {
                result = syntaxNode as T;
                return true;
            }

            return TryGetParentSyntax<T>(syntaxNode, out result);
        }
        catch
        {
            return false;
        }
    }
}

There is nothing overly complex going on here... it makes sense that the namespace would be "up" the syntax tree (because the class is contained within the namespace) so you simply need to travel "up" the syntax tree until you find the namespace and append that to the identifier of the ClassDeclarationSyntax.

Upvotes: 11

Arci
Arci

Reputation: 588

Try this code

public static string GetFullName(NamespaceDeclarationSyntax node)
{
    if (node.Parent is NamespaceDeclarationSyntax)
        return String.Format("{0}.{1}",
            GetFullName((NamespaceDeclarationSyntax)node.Parent),
            ((IdentifierNameSyntax)node.Name).Identifier.ToString());
    else
        return ((IdentifierNameSyntax)node.Name).Identifier.ToString();
}

Upvotes: 1

mynkow
mynkow

Reputation: 4548

Here is how I get the namespace. You have to slightly modify my code for your case:

        public static async Task<NamespaceDeclarationSyntax> GetNamespaceAsync(this Document document, CancellationToken cancellationToken = default(CancellationToken))
        {
            SyntaxNode documentRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var rootCompUnit = (CompilationUnitSyntax)documentRoot;
            return (NamespaceDeclarationSyntax)rootCompUnit.Members.Where(m => m.IsKind(SyntaxKind.NamespaceDeclaration)).Single();
        }

Upvotes: 0

Related Questions