locked
Visual Studio extension: how to sort usings by assembly? RRS feed

  • Question

  • Default "Remove And Sort Usings" command can only sort by alphabetical order. And it removes unused usings that is bad.

    So, I want to make an extension sorting in order: System, .Net assemblies,  other assemblies and my projects.

    Now I wrote the next:

    ProjectItem projectItem = dte2.ActiveDocument.ProjectItem;
    FileCodeModel2 fileCodeModel = (FileCodeModel2) projectItem.FileCodeModel;
    var imports = Flatten( fileCodeModel.CodeElements ).Where( i => i.Kind == vsCMElement.vsCMElementImportStmt ).Cast<CodeImport>(); // all imports of current document
    
    
    VSProject vsProject = (VSProject) projectItem.ContainingProject.Object;
    Reference[] references = vsProject.References.Cast<Reference>()
    .OrderBy( i => !i.Path.Contains( "\\.NETFramework\\" ) )
    .ThenBy( i => !i.Path.Contains( "\\UnityAssemblies\\" ) )
    .ThenBy( i => !i.Path.Contains( "\\Plugins\\" ) )
    .ThenBy( i => i.Path ).ToArray(); // all dependencies of current project

    Then I could use ReflectionOnlyLoad to get all types and namespaces. And to make ordered list of all namespaces and then sort document's namespaces via this list. 
    But I think Visual Studio should know about all types and namespaces. Am I right? Can I access those data?

    Or are there any other suggestions?



    Monday, June 26, 2017 9:29 AM

All replies

  • Hi Deni35,

    Welcome to the MSDN forum.

    Refer to your description, your issue is about VS extension development. Since our forum is to discuss the VS IDE, I will help you move this thread to the appropriate forum: Visual Studio Development Visual Studio Integrate to seek for a more professional support, thank you for your understanding.

    Best regards,

    Sara


    MSDN Community Support<br/> Please remember to click &quot;Mark as Answer&quot; the responses that resolved your issue, and to click &quot;Unmark as Answer&quot; if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact <a href="mailto:MSDNFSF@microsoft.com">MSDNFSF@microsoft.com</a>.

    Tuesday, June 27, 2017 1:55 AM
  • Hi Deni35,

    >>Or are there any other suggestions?

    It seems that you could also find all usings via Roslyn and sort them.

    DTE2 dte2 = (DTE2)this.ServiceProvider.GetService(typeof(DTE)); ////EnvDTE.Project currentProject = dte.Solution.Projects.Item(1); //ProjectItem projectItem = dte2.ActiveDocument.ProjectItem; //FileCodeModel2 fileCodeModel = (FileCodeModel2)projectItem.FileCodeModel; ////var imports = Flatten(fileCodeModel.CodeElements).Where(i => i.Kind == vsCMElement.vsCMElementImportStmt).Cast<CodeImport>(); // all imports of current document EnvDTE.Document doc = dte2.ActiveDocument; EnvDTE.TextDocument tdoc = (EnvDTE.TextDocument)doc.Object("TextDocument"); EnvDTE.EditPoint objEditPt = tdoc.StartPoint.CreateEditPoint(); string text = objEditPt.GetText(tdoc.EndPoint); SyntaxTree tree = CSharpSyntaxTree.ParseText(text); IEnumerable<SyntaxNode> nodes = ((CompilationUnitSyntax)tree.GetRoot()).DescendantNodes(); List<UsingDirectiveSyntax> usings = nodes .OfType<UsingDirectiveSyntax>().ToList();

    //sort usings as you want.

    For more information, please refer to the following code:

    https://github.com/HellBrick/HellBrick.Diagnostics/blob/master/src/HellBrick.Diagnostics/UnusedReferences/UnusedReferencesAnalyzer.cs

    Best regards,

    Cole Wu


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, June 27, 2017 7:20 AM
  • Thanks, Sara Liu. I was looking for something like Visual Studio Extension Development but found only  Visual Studio Development ))

    I can get all namespaces and thair types:

            var codeModel = (CodeModel2) dte2.ActiveDocument.ProjectItem.ContainingProject.CodeModel;
            var namespaces = GetNamespaces( codeModel.CodeElements ).ToArray();
            var classes = namespaces.SelectMany( i => i.Members.OfType<CodeClass2>() ).ToArray();
    
    
            private static IEnumerable<CodeNamespace> GetNamespaces(CodeElements elements) {
                foreach (CodeNamespace child in elements.Cast<CodeElement2>().Where( i => i.Kind == vsCMElement.vsCMElementNamespace )) {
                    yield return child;
    
                    foreach (var i in GetNamespaces( child.Members )) yield return i;
                }
            }

    Also I can get all references:

    var project = (VSProject) dte2.ActiveDocument.ProjectItem.ContainingProject.Object;

    var references = project.References.Cast().ToArray();

    Now I need to link each namespace (or class) to its assembly (reference) to be able to sort namespaces by thair assembly (reference). Can I do it?



    • Edited by Deni35 Tuesday, June 27, 2017 1:49 PM
    Tuesday, June 27, 2017 1:16 PM
  • Hi cole wu! Thanks for example.

    Now I've done sorting namespaces by its name:

        static class UsingSortingHelper {
    
            public static void Sort(VisualStudioWorkspace workspace, EnvDTE.Document doc, ILoggerService logger) {
                var project = doc.ProjectItem.ContainingProject;
                var _project = workspace.CurrentSolution.Projects.Single( i => i.FilePath == project.FileName );
                var _doc = _project.Documents.Single( i => i.FilePath == doc.FullName );
    
                _doc = Sort( _doc );
    
                if (!workspace.TryApplyChanges( _doc.Project.Solution )) {
                    logger.WriteLine( "Can't apply changes" );
                }
            }
    
    
            private static Document Sort(Document doc) {
                SyntaxTree tree;
                if (!doc.TryGetSyntaxTree( out tree )) {
                    tree = doc.GetSyntaxTreeAsync().Result;
                }
    
                tree = Sort( tree.GetRoot() ).SyntaxTree;
                return doc.WithSyntaxRoot( tree.GetRoot() );
            }
    
    
            private static SyntaxNode Sort(SyntaxNode root) {
                var usingRemover = new UsingStatementRemover();
                root = usingRemover.Visit( root );
    
    
                var usings = usingRemover.usings.OrderBy( i => !i.Name.ToString().StartsWith( "System." ) )
                                                .ThenBy( i => i.Name.ToString() )
                                                .ToArray();
    
    
                var usingAdder = new UsingStatementAdder( usings );
                return usingAdder.Visit( root );
            }
    
        }
    
    
    
        class UsingStatementRemover : CSharpSyntaxRewriter {
    
            private readonly List<UsingDirectiveSyntax> _usings = new List<UsingDirectiveSyntax>();
            public UsingDirectiveSyntax[] usings => _usings.ToArray();
    
    
            public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) {
                _usings.Add( node );
                return null;
            }
    
        }
    
        class UsingStatementAdder : CSharpSyntaxRewriter {
    
            private readonly UsingDirectiveSyntax[] usings;
    
    
            public UsingStatementAdder(UsingDirectiveSyntax[] usings) {
                this.usings = usings;
            }
    
    
            public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node) {
                var _usings = SyntaxFactory.List( usings.Where( i => i.Parent.Kind() == SyntaxKind.CompilationUnit ) );
                node = node.WithUsings( _usings );
    
                return base.VisitCompilationUnit( node );
            }
    
            public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) {
                var _usings = SyntaxFactory.List( usings.Where( i => i.Parent.Kind() == SyntaxKind.NamespaceDeclaration ) );
                node = node.WithUsings( _usings );
    
                return base.VisitNamespaceDeclaration( node );
            }
    
        }

    But I wanted to sort by assembly: .NET assemblies,  other assemblies, solution and current project.

    So, I need to get all assemblies, projects and its namespaces.

    To get assemblies and projects I can use: Compilation.References. But I can't find a way to get namespaces from MetadataReference.

    Thursday, June 29, 2017 8:29 PM
  • I managed it.

            private SyntaxNode Sort(SyntaxNode root, SemanticModel model, Compilation compilation) {
                var usingRemover = new UsingStatementRemover();
                root = usingRemover.Visit( root );
    
    
                // OrderByDescending - true at first
                var usings = usingRemover.usings
                    .ToDictionary( i => i, i => model.GetSymbolInfo( i.Name ).Symbol )
                    .OrderByDescending( i => i.Key.Alias == null ) // without alias at first
                    .ThenByDescending( i => i.Key.StaticKeyword.ValueText == null ) // without static at first
    
                    .ThenBy( i => GetNamespaceOrder( i.Value, compilation ) )
                    .ThenByDescending( i => i.Value.ToString().Contains( "Utils" ) ) // Utils at first
                    .ThenByDescending( i => i.Value.ToString().Contains( "Helpers" ) ) // Helpers at first
                    .ThenBy( i => i.Value.ToString() )
                    .Select( i => i.Key )
                    .ToArray();
    
                var usingAdder = new UsingStatementAdder( usings );
                return usingAdder.Visit( root );
            }
    
    
    
            private int GetNamespaceOrder(ISymbol @namespace, Compilation compilation) {
                return @namespace.Locations.Min( i => GetNamespaceOrder( @namespace, i, compilation ) );
            }
    
            private int GetNamespaceOrder(ISymbol @namespace, Location location, Compilation compilation) {
                string path = GetLocationPath( location, compilation );
    
                if (location.IsInMetadata) {
                    if (path.Contains( "\\.NETFramework\\" )) return 0;
    
                    if (path.Contains( "\\UnityAssemblies\\" )) {
                        if (path.Contains( "\\UnityEditor." )) return 10;
                        if (path.Contains( "\\UnityEngine." )) return 10;
                        //if (path.Contains( "\\UnityEngine." )) return 11; // UnityEditor.dll contains UnityEngine namespace, so for UnityEngine UnityEditor value will be returned
                        return 11;
                    }
    
                    if (path.Contains( "\\Plugins\\" )) {
                        if (path.Contains( "ThirdParty\\" )) return 20;
                        if (path.Contains( "Framework\\" )) return 21;
                        return 22;
                    }
                } else {
                    if (path.Contains( "\\Plugins\\" )) {
                        if (path.Contains( "ThirdParty\\" )) return 50;
                        if (path.Contains( "Framework\\" )) return 51;
                        return 52;
                    }
                }
    
                return 100;
            }
    
            private static string GetLocationPath(Location location, Compilation compilation) {
                if (location.IsInSource) return location.SourceTree?.FilePath;
                if (location.IsInMetadata) return compilation.GetMetadataReference( location.MetadataModule.ContainingAssembly ).Display;
                return null;
            }


    Tuesday, July 4, 2017 10:20 PM