| /* |
| * Copyright 2000-2013 JetBrains s.r.o. |
| * |
| * 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.jetbrains.python.refactoring.changeSignature; |
| |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.changeSignature.ChangeInfo; |
| import com.intellij.refactoring.changeSignature.ChangeSignatureUsageProcessor; |
| import com.intellij.refactoring.changeSignature.ParameterInfo; |
| import com.intellij.refactoring.rename.RenameUtil; |
| import com.intellij.refactoring.rename.ResolveSnapshotProvider; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.util.Query; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.containers.MultiMap; |
| import com.jetbrains.python.PyNames; |
| import com.jetbrains.python.PythonLanguage; |
| import com.jetbrains.python.documentation.PyDocstringGenerator; |
| import com.jetbrains.python.documentation.PyDocumentationSettings; |
| import com.jetbrains.python.editor.PythonDocCommentUtil; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; |
| import com.jetbrains.python.refactoring.PyRefactoringUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * User : ktisha |
| */ |
| |
| public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProcessor { |
| |
| private boolean useKeywords = false; |
| private boolean isMethod = false; |
| private boolean isAfterStar = false; |
| |
| @Override |
| public UsageInfo[] findUsages(ChangeInfo info) { |
| if (info instanceof PyChangeInfo) { |
| final List<UsageInfo> usages = PyRefactoringUtil.findUsages(((PyChangeInfo)info).getMethod(), true); |
| final Query<PyFunction> search = PyOverridingMethodsSearch.search(((PyChangeInfo)info).getMethod(), true); |
| final Collection<PyFunction> functions = search.findAll(); |
| for (PyFunction function : functions) { |
| usages.add(new UsageInfo(function)); |
| usages.addAll(PyRefactoringUtil.findUsages(function, true)); |
| } |
| return usages.toArray(new UsageInfo[usages.size()]); |
| } |
| return UsageInfo.EMPTY_ARRAY; |
| } |
| |
| @Nullable |
| @Override |
| public MultiMap<PsiElement, String> findConflicts(ChangeInfo info, Ref<UsageInfo[]> refUsages) { |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| if (info instanceof PyChangeInfo && info.isNameChanged()) { |
| final PyFunction function = ((PyChangeInfo)info).getMethod(); |
| final PyClass clazz = function.getContainingClass(); |
| if (clazz != null && clazz.findMethodByName(info.getNewName(), true) != null) { |
| conflicts.putValue(function, RefactoringBundle.message("method.0.is.already.defined.in.the.1", |
| info.getNewName(), |
| "class " + clazz.getQualifiedName())); |
| } |
| } |
| return conflicts; |
| } |
| |
| @Override |
| public boolean processUsage(final ChangeInfo changeInfo, |
| UsageInfo usageInfo, |
| boolean beforeMethodChange, |
| final UsageInfo[] usages) { |
| if (!isPythonUsage(usageInfo)) return false; |
| if (!(changeInfo instanceof PyChangeInfo)) return false; |
| if (!beforeMethodChange) return false; |
| PsiElement element = usageInfo.getElement(); |
| |
| if (changeInfo.isNameChanged()) { |
| final PsiElement method = changeInfo.getMethod(); |
| RenameUtil.doRenameGenericNamedElement(method, changeInfo.getNewName(), usages, null); |
| } |
| if (element == null) return false; |
| if (element.getParent() instanceof PyCallExpression) { |
| final PyCallExpression call = (PyCallExpression)element.getParent(); |
| final PyArgumentList argumentList = call.getArgumentList(); |
| if (argumentList != null) { |
| final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(element.getProject()); |
| StringBuilder builder = buildSignature((PyChangeInfo)changeInfo, call); |
| |
| final PyExpression newCall; |
| if (call instanceof PyDecorator) { |
| newCall = elementGenerator.createDecoratorList("@" + builder.toString()).getDecorators()[0]; |
| } |
| else |
| newCall = elementGenerator.createExpressionFromText(LanguageLevel.forElement(element), builder.toString()); |
| call.replace(newCall); |
| |
| return true; |
| } |
| } |
| else if (element instanceof PyFunction) { |
| processFunctionDeclaration((PyChangeInfo)changeInfo, (PyFunction)element); |
| } |
| return false; |
| } |
| |
| private StringBuilder buildSignature(PyChangeInfo changeInfo, PyCallExpression call) { |
| final PyArgumentList argumentList = call.getArgumentList(); |
| final PyExpression callee = call.getCallee(); |
| String name = callee != null ? callee.getText() : changeInfo.getNewName(); |
| StringBuilder builder = new StringBuilder(name + "("); |
| if (argumentList != null) { |
| final PyParameterInfo[] newParameters = changeInfo.getNewParameters(); |
| List<String> params = collectParameters(newParameters, argumentList); |
| builder.append(StringUtil.join(params, ",")); |
| } |
| builder.append(")"); |
| return builder; |
| } |
| |
| |
| private List<String> collectParameters(final PyParameterInfo[] newParameters, |
| @NotNull final PyArgumentList argumentList) { |
| useKeywords = false; |
| isMethod = false; |
| isAfterStar = false; |
| List<String> params = new ArrayList<String>(); |
| |
| int currentIndex = 0; |
| final PyExpression[] arguments = argumentList.getArguments(); |
| |
| for (PyParameterInfo info : newParameters) { |
| int oldIndex = calculateOldIndex(info); |
| final String parameterName = info.getName(); |
| if (parameterName.equals(PyNames.CANONICAL_SELF) || parameterName.equals("*")) { |
| continue; |
| } |
| |
| if (parameterName.startsWith("**")) { |
| currentIndex = addKwArgs(params, arguments, currentIndex); |
| } |
| else if (parameterName.startsWith("*")) { |
| currentIndex = addPositionalContainer(params, arguments, currentIndex); |
| } |
| else if (oldIndex < 0) { |
| addNewParameter(params, info); |
| currentIndex += 1; |
| } |
| else { |
| currentIndex = moveParameter(params, argumentList, info, currentIndex, oldIndex, arguments); |
| } |
| } |
| return params; |
| } |
| |
| private int calculateOldIndex(ParameterInfo info) { |
| if (info.getName().equals(PyNames.CANONICAL_SELF)) { |
| isMethod = true; |
| } |
| if (info.getName().equals("*")) { |
| isAfterStar = true; |
| useKeywords = true; |
| } |
| int oldIndex = info.getOldIndex(); |
| oldIndex = isMethod ? oldIndex - 1 : oldIndex; |
| oldIndex = isAfterStar ? oldIndex - 1 : oldIndex; |
| return oldIndex; |
| } |
| |
| |
| private static int addPositionalContainer(List<String> params, |
| PyExpression[] arguments, |
| int index) { |
| for (int i = index; i != arguments.length; ++i) { |
| if (!(arguments[i] instanceof PyKeywordArgument)) { |
| params.add(arguments[i].getText()); |
| index += 1; |
| } |
| } |
| return index; |
| } |
| |
| private static int addKwArgs(List<String> params, PyExpression[] arguments, int index) { |
| for (int i = index; i < arguments.length; ++i) { |
| if (arguments[i] instanceof PyKeywordArgument) { |
| params.add(arguments[i].getText()); |
| index += 1; |
| } |
| } |
| return index; |
| } |
| |
| private void addNewParameter(List<String> params, PyParameterInfo info) { |
| if (info.getDefaultInSignature()) { |
| useKeywords = true; |
| } |
| else { |
| params.add(useKeywords ? info.getName() + " = " + info.getDefaultValue() : info.getDefaultValue()); |
| } |
| } |
| |
| /** |
| * @return current index in argument list |
| */ |
| private int moveParameter(List<String> params, |
| PyArgumentList argumentList, |
| PyParameterInfo info, |
| int currentIndex, |
| int oldIndex, |
| PyExpression[] arguments) { |
| final String paramName = info.getOldName(); |
| final PyKeywordArgument keywordArgument = argumentList.getKeywordArgument(paramName); |
| if (keywordArgument != null) { |
| params.add(keywordArgument.getText()); |
| useKeywords = true; |
| return currentIndex + 1; |
| } |
| else if (currentIndex < arguments.length) { |
| final PyExpression currentParameter = arguments[currentIndex]; |
| if (currentParameter instanceof PyKeywordArgument && info.isRenamed()) { |
| params.add(currentParameter.getText()); |
| } |
| else if (oldIndex < arguments.length && ( |
| !(info.getDefaultInSignature() && arguments[oldIndex].getText().equals(info.getDefaultValue())) || !(currentParameter instanceof PyKeywordArgument))) { |
| return addOldPositionParameter(params, arguments[oldIndex], info, currentIndex); |
| } |
| else |
| return currentIndex; |
| } |
| else if (oldIndex < arguments.length) { |
| return addOldPositionParameter(params, arguments[oldIndex], info, currentIndex); |
| } |
| else if (!info.getDefaultInSignature()) { |
| params.add( useKeywords ? paramName + " = " + info.getDefaultValue() |
| : info.getDefaultValue()); |
| } |
| else { |
| useKeywords = true; |
| return currentIndex; |
| } |
| return currentIndex + 1; |
| } |
| |
| private int addOldPositionParameter(List<String> params, |
| PyExpression argument, |
| PyParameterInfo info, int currentIndex) { |
| final String paramName = info.getName(); |
| if (argument instanceof PyKeywordArgument) { |
| final PyExpression valueExpression = ((PyKeywordArgument)argument).getValueExpression(); |
| |
| if (!paramName.equals(argument.getName()) && !StringUtil.isEmptyOrSpaces(info.getDefaultValue())) { |
| if (!info.getDefaultInSignature()) |
| params.add(useKeywords ? info.getName() + " = " + info.getDefaultValue() : info.getDefaultValue()); |
| else |
| return currentIndex; |
| } |
| else { |
| params.add(valueExpression == null ? paramName : paramName + " = " + valueExpression.getText()); |
| useKeywords = true; |
| } |
| } |
| else { |
| params.add(useKeywords && !argument.getText().equals(info.getDefaultValue())? paramName + " = " + argument.getText() : argument.getText()); |
| } |
| return currentIndex + 1; |
| } |
| |
| private static boolean isPythonUsage(UsageInfo info) { |
| final PsiElement element = info.getElement(); |
| if (element == null) return false; |
| return element.getLanguage() == PythonLanguage.getInstance(); |
| } |
| |
| |
| @Override |
| public boolean processPrimaryMethod(ChangeInfo changeInfo) { |
| if (changeInfo instanceof PyChangeInfo && changeInfo.getLanguage().is(PythonLanguage.getInstance())) { |
| final PyChangeInfo pyChangeInfo = (PyChangeInfo)changeInfo; |
| processFunctionDeclaration(pyChangeInfo, pyChangeInfo.getMethod()); |
| return true; |
| } |
| return false; |
| } |
| |
| private static void processFunctionDeclaration(@NotNull PyChangeInfo changeInfo, @NotNull PyFunction function) { |
| if (changeInfo.isParameterNamesChanged()) { |
| final PyParameterInfo[] parameters = changeInfo.getNewParameters(); |
| for (int i = 0; i != parameters.length; ++i) { |
| PyParameterInfo paramInfo = parameters[i]; |
| final PyParameter[] oldParameters = function.getParameterList().getParameters(); |
| if (paramInfo.getOldIndex() >= 0 && paramInfo.isRenamed()) { |
| final UsageInfo[] usages = RenameUtil.findUsages(oldParameters[paramInfo.getOldIndex()], paramInfo.getName(), true, false, null); |
| for (UsageInfo info : usages) { |
| RenameUtil.rename(info, paramInfo.getName()); |
| } |
| } |
| } |
| } |
| if (changeInfo.isParameterSetOrOrderChanged()) { |
| fixDoc(changeInfo, function); |
| updateParameterList(changeInfo, function); |
| } |
| if (changeInfo.isNameChanged()) { |
| RenameUtil.doRenameGenericNamedElement(function, changeInfo.getNewName(), UsageInfo.EMPTY_ARRAY, null); |
| } |
| } |
| |
| private static void fixDoc(PyChangeInfo changeInfo, @NotNull PyFunction function) { |
| final PyStringLiteralExpression docStringExpression = function.getDocStringExpression(); |
| if (docStringExpression == null) return; |
| final PyParameterInfo[] parameters = changeInfo.getNewParameters(); |
| Set<String> names = new HashSet<String>(); |
| for (PyParameterInfo info : parameters) { |
| names.add(info.getName()); |
| } |
| final Module module = ModuleUtilCore.findModuleForPsiElement(function); |
| if (module == null) return; |
| for (PyParameter p : function.getParameterList().getParameters()) { |
| final String paramName = p.getName(); |
| if (!names.contains(paramName) && paramName != null) { |
| PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(module); |
| String prefix = documentationSettings.isEpydocFormat(docStringExpression.getContainingFile()) ? "@" : ":"; |
| final String replacement = PythonDocCommentUtil.removeParamFromDocstring(docStringExpression.getText(), prefix, |
| paramName); |
| PyExpression str = |
| PyElementGenerator.getInstance(function.getProject()).createDocstring(replacement).getExpression(); |
| docStringExpression.replace(str); |
| } |
| } |
| } |
| |
| private static void updateParameterList(PyChangeInfo changeInfo, PyFunction baseMethod) { |
| final PsiElement parameterList = baseMethod.getParameterList(); |
| |
| final PyParameterInfo[] parameters = changeInfo.getNewParameters(); |
| StringBuilder builder = new StringBuilder("def foo("); |
| final PyStringLiteralExpression docstring = baseMethod.getDocStringExpression(); |
| final PyParameter[] oldParameters = baseMethod.getParameterList().getParameters(); |
| for (int i = 0; i != parameters.length; ++i) { |
| PyParameterInfo info = parameters[i]; |
| |
| final int oldIndex = info.getOldIndex(); |
| if (i != 0 && oldIndex < oldParameters.length) { |
| builder.append(", "); |
| } |
| |
| if (docstring != null && oldIndex < 0) { |
| final String replacement = |
| new PyDocstringGenerator(baseMethod).withParam("param", info.getName()).docStringAsText(); |
| PyExpression str = |
| PyElementGenerator.getInstance(baseMethod.getProject()).createDocstring(replacement).getExpression(); |
| docstring.replace(str); |
| } |
| |
| if (oldIndex < oldParameters.length) |
| builder.append(info.getName()); |
| if (oldIndex >= 0 && oldIndex < oldParameters.length) { |
| final PyParameter parameter = oldParameters[oldIndex]; |
| if (parameter instanceof PyNamedParameter) { |
| final PyAnnotation annotation = ((PyNamedParameter)parameter).getAnnotation(); |
| if (annotation != null) { |
| builder.append(annotation.getText()); |
| } |
| } |
| } |
| final String defaultValue = info.getDefaultValue(); |
| if (defaultValue != null && info.getDefaultInSignature() && !StringUtil.isEmpty(defaultValue)) { |
| builder.append(" = ").append(defaultValue); |
| } |
| |
| } |
| builder.append("): pass"); |
| |
| final PyParameterList parameterList1 = |
| PyElementGenerator.getInstance(baseMethod.getProject()) |
| .createFromText(LanguageLevel.forElement(baseMethod), PyFunction.class, |
| builder.toString()).getParameterList(); |
| parameterList.replace(parameterList1); |
| } |
| |
| @Override |
| public boolean shouldPreviewUsages(ChangeInfo changeInfo, UsageInfo[] usages) { |
| return false; |
| } |
| |
| @Override |
| public boolean setupDefaultValues(ChangeInfo changeInfo, Ref<UsageInfo[]> refUsages, Project project) { |
| return true; |
| } |
| |
| @Override |
| public void registerConflictResolvers(List<ResolveSnapshotProvider.ResolveSnapshot> snapshots, |
| @NotNull ResolveSnapshotProvider resolveSnapshotProvider, |
| UsageInfo[] usages, ChangeInfo changeInfo) { |
| } |
| } |