All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureUsageProcessor Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition python-community library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 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 usages = PyRefactoringUtil.findUsages(((PyChangeInfo)info).getMethod(), true);
      final Query search = PyOverridingMethodsSearch.search(((PyChangeInfo)info).getMethod(), true);
      final Collection 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 findConflicts(ChangeInfo info, Ref refUsages) {
    final MultiMap conflicts = new MultiMap();
    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 params = collectParameters(newParameters, argumentList);
      builder.append(StringUtil.join(params, ","));
    }
    builder.append(")");
    return builder;
  }


  private List collectParameters(final PyParameterInfo[] newParameters,
                                                @NotNull final PyArgumentList argumentList) {
    useKeywords = false;
    isMethod = false;
    isAfterStar = false;
    List params = new ArrayList();

    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 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 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 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 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 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 PyParameter[] oldParameters = function.getParameterList().getParameters();
      for (PyParameterInfo paramInfo: changeInfo.getNewParameters()) {
        if (paramInfo.getOldIndex() >= 0 && paramInfo.isRenamed()) {
          final String newName = StringUtil.trimLeading(paramInfo.getName(), '*').trim();
          final UsageInfo[] usages = RenameUtil.findUsages(oldParameters[paramInfo.getOldIndex()], newName, true, false, null);
          for (UsageInfo info : usages) {
            RenameUtil.rename(info, newName);
          }
        }
      }
    }
    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 names = new HashSet();
    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();
    final StringBuilder builder = new StringBuilder("def foo(");
    final PyStringLiteralExpression docstring = baseMethod.getDocStringExpression();
    final PyParameter[] oldParameters = baseMethod.getParameterList().getParameters();
    final PyElementGenerator generator = PyElementGenerator.getInstance(baseMethod.getProject());
    for (int i = 0; i != parameters.length; ++i) {
      final 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();
        final PyExpression newDocstring = generator.createDocstring(replacement).getExpression();
        docstring.replace(newDocstring);
      }

      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.isNotEmpty(defaultValue)) {
        builder.append(" = ").append(defaultValue);
      }

    }
    builder.append("): pass");

    final PyParameterList newParameterList = generator.createFromText(LanguageLevel.forElement(baseMethod),
                                                                      PyFunction.class,
                                                                      builder.toString()).getParameterList();
    parameterList.replace(newParameterList);
  }

  @Override
  public boolean shouldPreviewUsages(ChangeInfo changeInfo, UsageInfo[] usages) {
    return false;
  }

  @Override
  public boolean setupDefaultValues(ChangeInfo changeInfo, Ref refUsages, Project project) {
    return true;
  }

  @Override
  public void registerConflictResolvers(List snapshots,
                                        @NotNull ResolveSnapshotProvider resolveSnapshotProvider,
                                        UsageInfo[] usages, ChangeInfo changeInfo) {
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy