/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * 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 com.goide.refactor; import com.goide.GoLanguage; import com.goide.psi.*; import com.goide.psi.impl.GoPsiImplUtil; import com.goide.psi.impl.GoTypeUtil; import com.intellij.codeInsight.PsiEquivalenceUtil; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.codeInsight.template.Expression; import com.intellij.codeInsight.template.ExpressionContext; import com.intellij.codeInsight.template.Result; import com.intellij.codeInsight.template.TextResult; import com.intellij.lang.LanguageNamesValidation; import com.intellij.lang.refactoring.NamesValidator; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiRecursiveElementVisitor; import com.intellij.psi.codeStyle.NameUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.UniqueNameGenerator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class GoRefactoringUtil { private GoRefactoringUtil() {} @NotNull public static List<PsiElement> getLocalOccurrences(@NotNull PsiElement element) { return getOccurrences(element, PsiTreeUtil.getTopmostParentOfType(element, GoBlock.class)); } @NotNull public static List<PsiElement> getOccurrences(@NotNull PsiElement pattern, @Nullable PsiElement context) { if (context == null) return Collections.emptyList(); List<PsiElement> occurrences = ContainerUtil.newArrayList(); PsiRecursiveElementVisitor visitor = new PsiRecursiveElementVisitor() { @Override public void visitElement(@NotNull PsiElement element) { if (PsiEquivalenceUtil.areElementsEquivalent(element, pattern)) { occurrences.add(element); return; } super.visitElement(element); } }; context.acceptChildren(visitor); return occurrences; } @Nullable public static PsiElement findLocalAnchor(@NotNull List<PsiElement> occurrences) { return findAnchor(occurrences, PsiTreeUtil.getNonStrictParentOfType(PsiTreeUtil.findCommonParent(occurrences), GoBlock.class)); } @Nullable public static PsiElement findAnchor(@NotNull List<PsiElement> occurrences, @Nullable PsiElement context) { PsiElement first = ContainerUtil.getFirstItem(occurrences); PsiElement statement = PsiTreeUtil.getNonStrictParentOfType(first, GoStatement.class); while (statement != null && statement.getParent() != context) { statement = statement.getParent(); } return statement == null ? GoPsiImplUtil.getTopLevelDeclaration(first) : statement; } public static LinkedHashSet<String> getSuggestedNames(GoExpression expression) { return getSuggestedNames(expression, expression); } @NotNull public static Expression createParameterNameSuggestedExpression(GoExpression expression) { GoTopLevelDeclaration topLevelDecl = PsiTreeUtil.getParentOfType(expression, GoTopLevelDeclaration.class); return new ParameterNameExpression(getSuggestedNames(expression, topLevelDecl != null ? topLevelDecl.getNextSibling() : null)); } private static class ParameterNameExpression extends Expression { private final Set<String> myNames; public ParameterNameExpression(@NotNull Set<String> names) { myNames = names; } @Nullable @Override public Result calculateResult(ExpressionContext context) { LookupElement firstElement = ArrayUtil.getFirstElement(calculateLookupItems(context)); return new TextResult(firstElement != null ? firstElement.getLookupString() : ""); } @Nullable @Override public Result calculateQuickResult(ExpressionContext context) { return null; } @NotNull @Override public LookupElement[] calculateLookupItems(ExpressionContext context) { int offset = context.getStartOffset(); Project project = context.getProject(); PsiDocumentManager.getInstance(project).commitAllDocuments(); assert context.getEditor() != null; PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(context.getEditor().getDocument()); assert file != null; PsiElement elementAt = file.findElementAt(offset); Set<String> parameterNames = ContainerUtil.newHashSet(); GoParameters parameters = PsiTreeUtil.getParentOfType(elementAt, GoParameters.class); if (parameters != null) { GoParamDefinition parameter = PsiTreeUtil.getParentOfType(elementAt, GoParamDefinition.class); for (GoParameterDeclaration paramDecl : parameters.getParameterDeclarationList()) { for (GoParamDefinition paramDef : paramDecl.getParamDefinitionList()) { if (parameter != paramDef) { parameterNames.add(paramDef.getName()); } } } } Set<LookupElement> set = ContainerUtil.newLinkedHashSet(); for (String name : myNames) { set.add(LookupElementBuilder.create(UniqueNameGenerator.generateUniqueName(name, parameterNames))); } return set.toArray(new LookupElement[set.size()]); } } @NotNull private static LinkedHashSet<String> getSuggestedNames(GoExpression expression, PsiElement context) { // todo rewrite with names resolve; check occurrences contexts if (expression.isEquivalentTo(context)) { context = PsiTreeUtil.getParentOfType(context, GoBlock.class); } LinkedHashSet<String> usedNames = getNamesInContext(context); LinkedHashSet<String> names = ContainerUtil.newLinkedHashSet(); NamesValidator namesValidator = LanguageNamesValidation.INSTANCE.forLanguage(GoLanguage.INSTANCE); if (expression instanceof GoCallExpr) { GoReferenceExpression callReference = PsiTreeUtil.getChildOfType(expression, GoReferenceExpression.class); if (callReference != null) { String name = StringUtil.decapitalize(callReference.getIdentifier().getText()); for (String candidate : NameUtil.getSuggestionsByName(name, "", "", false, false, false)) { if (usedNames.contains(candidate)) continue; if (!isValidName(namesValidator, candidate)) continue; names.add(candidate); } } } GoType type = expression.getGoType(null); String typeText = GoPsiImplUtil.getText(type); if (StringUtil.isNotEmpty(typeText)) { boolean array = GoTypeUtil.isIterable(type) && !GoTypeUtil.isString(type); for (String candidate : NameUtil.getSuggestionsByName(typeText, "", "", false, false, array)) { if (usedNames.contains(candidate) || typeText.equals(candidate)) continue; if (!isValidName(namesValidator, candidate)) continue; names.add(candidate); } } if (names.isEmpty()) { names.add(UniqueNameGenerator.generateUniqueName("i", usedNames)); } return names; } private static boolean isValidName(NamesValidator namesValidator, String candidate) { return namesValidator != null && !namesValidator.isKeyword(candidate, null) && namesValidator.isIdentifier(candidate, null); } @NotNull private static LinkedHashSet<String> getNamesInContext(PsiElement context) { if (context == null) return ContainerUtil.newLinkedHashSet(); LinkedHashSet<String> names = ContainerUtil.newLinkedHashSet(); for (GoNamedElement namedElement : PsiTreeUtil.findChildrenOfType(context, GoNamedElement.class)) { names.add(namedElement.getName()); } names.addAll(((GoFile)context.getContainingFile()).getImportMap().keySet()); GoFunctionDeclaration functionDeclaration = PsiTreeUtil.getParentOfType(context, GoFunctionDeclaration.class); GoSignature signature = PsiTreeUtil.getChildOfType(functionDeclaration, GoSignature.class); for (GoParamDefinition param : PsiTreeUtil.findChildrenOfType(signature, GoParamDefinition.class)) { names.add(param.getName()); } return names; } }