Accessing attribute info from DTE

前端 未结 2 1108
鱼传尺愫
鱼传尺愫 2020-12-29 20:41

I have coded something like the following:

[Attrib(typeof(MyCustomType))]
public class TargetType
{
  // .....
}

I want to use EnvDTE

相关标签:
2条回答
  • 2020-12-29 21:11

    Is there an easier way to do this?

    No, I don't think so, atleast for a <= VS2013, it seems that the CodeAttributeArgument doesn't go any further, which is shame. They should've released CodeAttributeArgument2 that has Value as CodeExpr :\..

    If you use >=VS2014, you can get access to Roslyn, and it should become easier - don't know exactly how you can access roslyn inside VS extension, have to wait and see.

    In order to get attributes, you can use VS helper:

    public List<CodeElement> GetAllCodeElementsOfType(
        CodeElements elements, 
        vsCMElement elementType, 
    
        bool includeExternalTypes)
    {
        var ret = new List<CodeElement>();
    
        foreach (CodeElement elem in elements)
        {
            // iterate all namespaces (even if they are external)
            // > they might contain project code
            if (elem.Kind == vsCMElement.vsCMElementNamespace)
            {
                ret.AddRange(
                    GetAllCodeElementsOfType(
                        ((CodeNamespace)elem).Members, 
                        elementType, 
                        includeExternalTypes));
            }
    
            // if its not a namespace but external
            // > ignore it
            else if (elem.InfoLocation == vsCMInfoLocation.vsCMInfoLocationExternal && !includeExternalTypes)
                continue;
    
            // if its from the project
            // > check its members
            else if (elem.IsCodeType)
            {
                ret.AddRange(
                    GetAllCodeElementsOfType(
                        ((CodeType)elem).Members, 
                        elementType, 
                        includeExternalTypes));
            }
    
            if (elem.Kind == elementType)
                ret.Add(elem);
        }
        return ret;
    }
    

    Original source: https://github.com/PombeirP/T4Factories/blob/master/T4Factories.Testbed/CodeTemplates/VisualStudioAutomationHelper.ttinclude

    In a meanwhile, you could use backtracking solution, this is not nice, but it should work, haven't tested it exactly 100%. The basic idea is to start tracking backwards from the class, and keep track of different namespaces/usings that arein the path of a class. This tries to simulate pretty much what a real compiler would do, if it's going to resolve a type:

     var solution = (Solution2) _applicationObject.Solution;
    var projects = solution.Projects;
    var activeProject = projects
        .OfType<Project>()
        .First();
    
    // locate my class.
    var myClass = GetAllCodeElementsOfType(
        activeProject.CodeModel.CodeElements,
        vsCMElement.vsCMElementClass, false)
        .OfType<CodeClass2>()
        .First(x => x.Name == "Program");
    
    // locate my attribute on class.
    var mySpecialAttrib = myClass
        .Attributes
        .OfType<CodeAttribute2>()
        .First();
    
    
    
    var attributeArgument = mySpecialAttrib.Arguments
        .OfType<CodeAttributeArgument>()
        .First();
    
    string myType = Regex.Replace(
        attributeArgument.Value, // typeof(MyType)
        "^typeof.*\\((.*)\\)$", "$1"); // MyType*/
    
    var codeNamespace = myClass.Namespace;
    var classNamespaces = new List<string>();
    
    while (codeNamespace != null)
    {
        var codeNs = codeNamespace;
        var namespaceName = codeNs.FullName;
    
        var foundNamespaces = new List<string> {namespaceName};
    
        // generate namespaces from usings.
        var @usings = codeNs.Children
            .OfType<CodeImport>()
            .Select(x =>
                new[]
                {
                    x.Namespace,
                    namespaceName + "." + x.Namespace
                })
            .SelectMany(x => x)
            .ToList();
    
        foundNamespaces.AddRange(@usings);
    
        // prepend all namespaces:
        var extra = (
            from ns2 in classNamespaces
            from ns1 in @usings
            select ns1 + "." + ns2)
            .ToList();
    
        classNamespaces.AddRange(foundNamespaces);
        classNamespaces.AddRange(extra);
    
        codeNamespace = codeNs.Parent as CodeNamespace;
        if (codeNamespace == null)
        {
            var codeModel = codeNs.Parent as FileCodeModel2;
            if (codeModel == null) return;
    
            var elems = codeModel.CodeElements;
            if (elems == null) continue;
    
            var @extraUsings = elems
                .OfType<CodeImport>()
                .Select(x => x.Namespace);
    
            classNamespaces.AddRange(@extraUsings);
        }
    }
    
    // resolve to a type!
    var typeLocator = new EnvDTETypeLocator();
    var resolvedType = classNamespaces.Select(type =>
            typeLocator.FindTypeExactMatch(activeProject, type + "." + myType))
        .FirstOrDefault(type => type != null);
    

    You need EnvDTETypeLocator too.

    For VS2015, an example of roslyn integration can be found from here: https://github.com/tomasr/roslyn-colorizer/blob/master/RoslynColorizer/RoslynColorizer.cs

    It'll definitely be A lot easier than it is with current CodeModel.

    0 讨论(0)
  • 2020-12-29 21:16

    You can try to use a way described Get all methods that are decorated with a specific attribute using T4/EnvDTE

    0 讨论(0)
提交回复
热议问题