/* * Copyright 2013-2017 consulo.io * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package consulo.csharp.lang; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredReadAction; import consulo.csharp.ide.codeInspection.unusedUsing.UnusedUsingVisitor; import consulo.csharp.lang.psi.CSharpFile; import consulo.csharp.lang.psi.CSharpFileFactory; import consulo.csharp.lang.psi.CSharpRecursiveElementVisitor; import consulo.csharp.lang.psi.CSharpStubElements; import consulo.csharp.lang.psi.CSharpTokens; import consulo.csharp.lang.psi.CSharpUsingListChild; import consulo.csharp.lang.psi.CSharpUsingNamespaceStatement; import consulo.csharp.lang.psi.CSharpUsingTypeStatement; import consulo.csharp.lang.psi.impl.source.CSharpTypeDefStatementImpl; import consulo.dotnet.psi.DotNetReferenceExpression; import consulo.dotnet.psi.DotNetType; import com.intellij.lang.ASTNode; import com.intellij.lang.ImportOptimizer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiRecursiveElementVisitor; import com.intellij.psi.TokenType; import com.intellij.psi.tree.IElementType; import com.intellij.util.containers.ContainerUtil; /** * @author VISTALL * @since 01.01.14. */ public class CSharpImportOptimizer implements ImportOptimizer { @Override public boolean supports(PsiFile psiFile) { return psiFile instanceof CSharpFile; } @NotNull @Override public Runnable processFile(final PsiFile psiFile) { return new CollectingInfoRunnable() { private int myCount = 0; @Nullable @Override public String getUserNotificationInfo() { if(myCount > 0) { return "removed " + myCount + " using statement" + (myCount != 1 ? "s" : ""); } return null; } @Override @RequiredReadAction public void run() { final UnusedUsingVisitor unusedUsingVisitor = new UnusedUsingVisitor(); PsiRecursiveElementVisitor visitor = new PsiRecursiveElementVisitor() { @Override public void visitElement(PsiElement element) { element.accept(unusedUsingVisitor); super.visitElement(element); } }; psiFile.accept(visitor); for(Map.Entry<CSharpUsingListChild, Boolean> entry : unusedUsingVisitor.getUsingContext().entrySet()) { if(entry.getValue()) { continue; } myCount++; entry.getKey().delete(); } final Set<CSharpUsingListChild> processedUsingStatements = new LinkedHashSet<CSharpUsingListChild>(); final List<List<CSharpUsingListChild>> usingLists = new ArrayList<List<CSharpUsingListChild>>(); psiFile.accept(new CSharpRecursiveElementVisitor() { @Override @RequiredReadAction public void visitUsingChild(@NotNull CSharpUsingListChild child) { super.visitUsingChild(child); if(processedUsingStatements.contains(child)) { return; } PsiElement referenceElement = child.getReferenceElement(); if(referenceElement == null) { return; } List<CSharpUsingListChild> children = new ArrayList<CSharpUsingListChild>(5); for(ASTNode node = child.getNode(); node != null; node = node.getTreeNext()) { IElementType elementType = node.getElementType(); if(elementType == TokenType.WHITE_SPACE) { CharSequence chars = node.getChars(); if(StringUtil.countNewLines(chars) > 2) { break; } } else if(elementType == CSharpTokens.PREPROCESSOR_DIRECTIVE) { break; } else if(CSharpStubElements.USING_CHILDREN.contains(elementType)) { children.add(node.getPsi(CSharpUsingListChild.class)); } } if(children.size() <= 1) { return; } usingLists.add(children); processedUsingStatements.addAll(children); } }); for(List<CSharpUsingListChild> usingList : usingLists) { formatAndReplace(psiFile, usingList); } } }; } @RequiredReadAction private static void formatAndReplace(@NotNull PsiFile file, @NotNull List<CSharpUsingListChild> children) { PsiElement parent = children.get(0).getParent(); Comparator<String> namespaceComparator = new Comparator<String>() { @Override public int compare(String o1, String o2) { boolean s1 = isSystem(o1); boolean s2 = isSystem(o2); if(s1 && s2 || !s1 && !s2) { return o1.compareToIgnoreCase(o2); } else { if(s1) { return -1; } if(s2) { return 1; } return 0; } } }; Set<String> namespaceUse = new TreeSet<String>(namespaceComparator); Set<String> typeUse = new TreeSet<String>(namespaceComparator); List<Pair<String, String>> typeDef = new ArrayList<Pair<String, String>>(); for(CSharpUsingListChild statement : children) { if(statement instanceof CSharpUsingNamespaceStatement) { DotNetReferenceExpression namespaceReference = ((CSharpUsingNamespaceStatement) statement).getNamespaceReference(); if(namespaceReference == null) // if using dont have reference - dont format it { return; } namespaceUse.add(namespaceReference.getText()); } else if(statement instanceof CSharpTypeDefStatementImpl) { DotNetType type = ((CSharpTypeDefStatementImpl) statement).getType(); if(type == null) { return; } typeDef.add(new Pair<String, String>(((CSharpTypeDefStatementImpl) statement).getName(), type.getText())); } else if(statement instanceof CSharpUsingTypeStatement) { DotNetType type = ((CSharpUsingTypeStatement) statement).getType(); if(type == null) { return; } typeUse.add(type.getText()); } } StringBuilder builder = new StringBuilder(); if(!typeUse.isEmpty()) { for(String qName : typeUse) { builder.append("using static ").append(qName).append(";\n"); } if(!namespaceUse.isEmpty() || namespaceUse.isEmpty() && !typeDef.isEmpty()) { builder.append("\n"); } } if(!namespaceUse.isEmpty()) { for(String qName : namespaceUse) { builder.append("using ").append(qName).append(";\n"); } if(!namespaceUse.isEmpty() && !typeDef.isEmpty()) { builder.append("\n"); } } if(!typeDef.isEmpty()) { for(Pair<String, String> pair : typeDef) { builder.append("using ").append(pair.getFirst()).append(" = ").append(pair.getSecond()).append(";\n"); } } CSharpFile newFile = CSharpFileFactory.createFile(file.getProject(), builder); CSharpUsingListChild[] usingStatements = newFile.getUsingStatements(); PsiElement before = ContainerUtil.getLastItem(children).getNextSibling(); parent.deleteChildRange(children.get(0), children.get(children.size() - 1)); parent.addRangeBefore(usingStatements[0], usingStatements[usingStatements.length - 1], before); } private static boolean isSystem(String name) { return name.startsWith("System"); } }