2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.refactoring.makeFunctionTopLevel;
18 import com.intellij.codeInsight.controlflow.ControlFlow;
19 import com.intellij.codeInsight.controlflow.Instruction;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.psi.PsiElement;
23 import com.intellij.psi.PsiFile;
24 import com.intellij.psi.PsiNamedElement;
25 import com.intellij.psi.util.PsiTreeUtil;
26 import com.intellij.refactoring.BaseRefactoringProcessor;
27 import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
28 import com.intellij.usageView.UsageInfo;
29 import com.intellij.usageView.UsageViewDescriptor;
30 import com.intellij.util.ArrayUtil;
31 import com.intellij.util.containers.HashSet;
32 import com.jetbrains.python.PyNames;
33 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
34 import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
35 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
36 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
37 import com.jetbrains.python.codeInsight.imports.AddImportHelper;
38 import com.jetbrains.python.psi.*;
39 import com.jetbrains.python.psi.impl.PyPsiUtils;
40 import com.jetbrains.python.psi.resolve.PyResolveContext;
41 import com.jetbrains.python.psi.types.TypeEvalContext;
42 import com.jetbrains.python.refactoring.PyRefactoringUtil;
43 import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
44 import org.jetbrains.annotations.NotNull;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.List;
51 import static com.jetbrains.python.psi.PyUtil.as;
54 * @author Mikhail Golubev
56 public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
57 protected final PyFunction myFunction;
58 protected final PsiFile mySourceFile;
59 protected final PyResolveContext myResolveContext;
60 protected final PyElementGenerator myGenerator;
61 protected final String myDestinationPath;
62 protected final List<PsiElement> myExternalReads = new ArrayList<PsiElement>();
64 public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destinationPath) {
65 super(targetFunction.getProject());
66 myFunction = targetFunction;
67 myDestinationPath = destinationPath;
68 final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
69 myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
70 myGenerator = PyElementGenerator.getInstance(myProject);
71 mySourceFile = myFunction.getContainingFile();
76 protected final UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
77 return new UsageViewDescriptorAdapter() {
80 public PsiElement[] getElements() {
81 return new PsiElement[] {myFunction};
85 public String getProcessedElementsHeader() {
86 return getRefactoringName();
93 protected final UsageInfo[] findUsages() {
94 return ArrayUtil.toObjectArray(PyRefactoringUtil.findUsages(myFunction, false), UsageInfo.class);
98 protected final String getCommandName() {
99 return getRefactoringName();
103 protected final void performRefactoring(@NotNull UsageInfo[] usages) {
104 final List<String> newParameters = collectNewParameterNames();
106 assert ApplicationManager.getApplication().isWriteAccessAllowed();
108 // We should update usages before we generate and insert new function, because we have to update its usages inside
109 // (e.g. recursive calls) it first
110 updateUsages(newParameters, usages);
111 final PyFile newFile = PyUtil.getOrCreateFile(myDestinationPath, myProject);
112 final PyFunction newFunction = insertFunction(createNewFunction(newParameters), newFile);
116 updateImports(newFunction, usages);
119 private void updateImports(@NotNull PyFunction newFunction, @NotNull UsageInfo[] usages) {
120 final Set<PsiFile> usageFiles = new HashSet<PsiFile>();
121 for (UsageInfo usage : usages) {
122 usageFiles.add(usage.getFile());
124 for (PsiFile file : usageFiles) {
125 if (file != newFunction.getContainingFile()) {
126 PyClassRefactoringUtil.insertImport(file, newFunction, null, true);
129 // References inside the body of function
130 if (newFunction.getContainingFile() != mySourceFile) {
131 for (PsiElement read : myExternalReads) {
132 if (read instanceof PsiNamedElement && read.isValid()) {
133 PyClassRefactoringUtil.insertImport(newFunction, (PsiNamedElement)read, null, true);
136 PyClassRefactoringUtil.optimizeImports(mySourceFile);
141 protected abstract String getRefactoringName();
144 protected abstract List<String> collectNewParameterNames();
146 protected abstract void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages);
149 protected abstract PyFunction createNewFunction(@NotNull Collection<String> newParamNames);
152 protected final PyParameterList addParameters(@NotNull PyParameterList paramList, @NotNull Collection<String> newParameters) {
153 if (!newParameters.isEmpty()) {
154 final String commaSeparatedNames = StringUtil.join(newParameters, ", ");
155 final StringBuilder paramListText = new StringBuilder(paramList.getText());
156 paramListText.insert(1, commaSeparatedNames + (paramList.getParameters().length > 0 ? ", " : ""));
157 final PyParameterList newElement = myGenerator.createParameterList(LanguageLevel.forElement(myFunction), paramListText.toString());
158 return (PyParameterList)paramList.replace(newElement);
164 protected PyArgumentList addArguments(@NotNull PyArgumentList argList, @NotNull Collection<String> newArguments) {
165 if (!newArguments.isEmpty()) {
166 final String commaSeparatedNames = StringUtil.join(newArguments, ", ");
167 final StringBuilder argListText = new StringBuilder(argList.getText());
168 argListText.insert(1, commaSeparatedNames + (argList.getArguments().length > 0 ? ", " : ""));
169 final PyArgumentList newElement = myGenerator.createArgumentList(LanguageLevel.forElement(argList), argListText.toString());
170 return (PyArgumentList)argList.replace(newElement);
176 protected PyFunction insertFunction(@NotNull PyFunction newFunction, PyFile newFile) {
177 final PyFunction replacement;
178 if (mySourceFile == newFile) {
179 final PsiElement anchor;
180 anchor = PyPsiUtils.getParentRightBefore(myFunction, mySourceFile);
181 replacement = (PyFunction)mySourceFile.addAfter(newFunction, anchor);
184 replacement = (PyFunction)newFile.addAfter(newFunction, AddImportHelper.getFileInsertPosition(newFile));
190 protected AnalysisResult analyseScope(@NotNull ScopeOwner owner) {
191 final ControlFlow controlFlow = ControlFlowCache.getControlFlow(owner);
192 final AnalysisResult result = new AnalysisResult();
193 for (Instruction instruction : controlFlow.getInstructions()) {
194 if (instruction instanceof ReadWriteInstruction) {
195 final ReadWriteInstruction readWriteInstruction = (ReadWriteInstruction)instruction;
196 final PsiElement element = readWriteInstruction.getElement();
197 if (element == null) {
200 if (readWriteInstruction.getAccess().isReadAccess()) {
201 for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
202 if (resolved != null) {
203 if (isInitOrNewMethod(resolved)) {
204 resolved = ((PyFunction)resolved).getContainingClass();
206 if (isFromEnclosingScope(resolved)) {
207 result.readsFromEnclosingScope.add(element);
209 else if (!PsiTreeUtil.isAncestor(myFunction, resolved, false)) {
210 myExternalReads.add(resolved);
212 if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf()) {
213 if (PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
214 result.readsOfSelfParameter.add(element);
216 else if (!PsiTreeUtil.isAncestor(myFunction, resolved, true)) {
217 result.readsOfSelfParametersFromEnclosingScope.add(element);
223 if (readWriteInstruction.getAccess().isWriteAccess() && element instanceof PyTargetExpression) {
224 for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
225 if (resolved != null) {
226 if (element.getParent() instanceof PyNonlocalStatement && isFromEnclosingScope(resolved)) {
227 result.nonlocalWritesToEnclosingScope.add((PyTargetExpression)element);
229 if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf() &&
230 PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
231 result.writesToSelfParameter.add((PyTargetExpression)element);
241 private static boolean isInitOrNewMethod(@NotNull PsiElement elem) {
242 final PyFunction func = as(elem, PyFunction.class);
243 return func != null && (PyNames.INIT.equals(func.getName()) || PyNames.NEW.equals(func.getName()));
246 private boolean isFromEnclosingScope(@NotNull PsiElement element) {
247 return element.getContainingFile() == mySourceFile &&
248 !PsiTreeUtil.isAncestor(myFunction, element, false) &&
249 !(ScopeUtil.getScopeOwner(element) instanceof PsiFile);
252 protected static class AnalysisResult {
253 final List<PsiElement> readsFromEnclosingScope = new ArrayList<>();
254 final List<PyTargetExpression> nonlocalWritesToEnclosingScope = new ArrayList<>();
255 final List<PsiElement> readsOfSelfParametersFromEnclosingScope = new ArrayList<>();
256 final List<PsiElement> readsOfSelfParameter = new ArrayList<>();
257 // No one writes to "self", but handle this case too just to be sure
258 final List<PyTargetExpression> writesToSelfParameter = new ArrayList<>();