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.move.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.Condition;
22 import com.intellij.openapi.util.text.StringUtil;
23 import com.intellij.psi.PsiElement;
24 import com.intellij.psi.PsiFile;
25 import com.intellij.psi.PsiNamedElement;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.refactoring.BaseRefactoringProcessor;
28 import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
29 import com.intellij.usageView.UsageInfo;
30 import com.intellij.usageView.UsageViewDescriptor;
31 import com.intellij.util.ArrayUtil;
32 import com.intellij.util.IncorrectOperationException;
33 import com.intellij.util.containers.ContainerUtil;
34 import com.intellij.util.containers.HashSet;
35 import com.jetbrains.python.PyBundle;
36 import com.jetbrains.python.PyNames;
37 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
38 import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
39 import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
40 import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
41 import com.jetbrains.python.codeInsight.imports.AddImportHelper;
42 import com.jetbrains.python.psi.*;
43 import com.jetbrains.python.psi.impl.PyPsiUtils;
44 import com.jetbrains.python.psi.resolve.PyResolveContext;
45 import com.jetbrains.python.psi.types.TypeEvalContext;
46 import com.jetbrains.python.refactoring.PyRefactoringUtil;
47 import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
48 import com.jetbrains.python.refactoring.move.PyMoveRefactoringUtil;
49 import org.jetbrains.annotations.NotNull;
51 import java.util.ArrayList;
52 import java.util.Collection;
53 import java.util.List;
56 import static com.jetbrains.python.psi.PyUtil.as;
59 * @author Mikhail Golubev
61 public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
62 protected final PyFunction myFunction;
63 protected final PsiFile mySourceFile;
64 protected final PyResolveContext myResolveContext;
65 protected final PyElementGenerator myGenerator;
66 protected final String myDestinationPath;
67 protected final List<PsiElement> myExternalReads = new ArrayList<PsiElement>();
69 public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destinationPath) {
70 super(targetFunction.getProject());
71 myFunction = targetFunction;
72 myDestinationPath = destinationPath;
73 final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
74 myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
75 myGenerator = PyElementGenerator.getInstance(myProject);
76 mySourceFile = myFunction.getContainingFile();
81 protected final UsageViewDescriptor createUsageViewDescriptor(@NotNull UsageInfo[] usages) {
82 return new UsageViewDescriptorAdapter() {
85 public PsiElement[] getElements() {
86 return new PsiElement[] {myFunction};
90 public String getProcessedElementsHeader() {
91 return getRefactoringName();
98 protected final UsageInfo[] findUsages() {
99 return ArrayUtil.toObjectArray(PyRefactoringUtil.findUsages(myFunction, false), UsageInfo.class);
103 protected final String getCommandName() {
104 return getRefactoringName();
108 protected final void performRefactoring(@NotNull UsageInfo[] usages) {
109 final List<String> newParameters = collectNewParameterNames();
111 assert ApplicationManager.getApplication().isWriteAccessAllowed();
113 final PyFile targetFile = PyUtil.getOrCreateFile(myDestinationPath, myProject);
114 if (targetFile.findTopLevelFunction(myFunction.getName()) != null) {
115 throw new IncorrectOperationException(
116 PyBundle.message("refactoring.move.error.destination.file.contains.function.$0", myFunction.getName()));
118 if (importsRequired(usages, targetFile)) {
119 PyMoveRefactoringUtil.checkValidImportableFile(targetFile, targetFile.getVirtualFile());
122 // We should update usages before we generate and insert new function, because we have to update its usages inside
123 // (e.g. recursive calls) it first
124 updateUsages(newParameters, usages);
125 final PyFunction newFunction = insertFunction(createNewFunction(newParameters), targetFile);
129 updateImports(newFunction, usages);
132 private boolean importsRequired(@NotNull UsageInfo[] usages, final PyFile targetFile) {
133 return ContainerUtil.exists(usages, new Condition<UsageInfo>() {
135 public boolean value(UsageInfo info) {
136 final PsiElement element = info.getElement();
137 if (element == null) {
140 return !belongsToFunction(element) && info.getFile() != targetFile;
145 private boolean belongsToFunction(PsiElement element) {
146 return PsiTreeUtil.isAncestor(myFunction, element, false);
150 private void updateImports(@NotNull PyFunction newFunction, @NotNull UsageInfo[] usages) {
151 final Set<PsiFile> usageFiles = new HashSet<PsiFile>();
152 for (UsageInfo usage : usages) {
153 usageFiles.add(usage.getFile());
155 for (PsiFile file : usageFiles) {
156 if (file != newFunction.getContainingFile()) {
157 PyClassRefactoringUtil.insertImport(file, newFunction, null, true);
160 // References inside the body of function
161 if (newFunction.getContainingFile() != mySourceFile) {
162 for (PsiElement read : myExternalReads) {
163 if (read instanceof PsiNamedElement && read.isValid()) {
164 PyClassRefactoringUtil.insertImport(newFunction, (PsiNamedElement)read, null, true);
167 PyClassRefactoringUtil.optimizeImports(mySourceFile);
172 protected abstract String getRefactoringName();
175 protected abstract List<String> collectNewParameterNames();
177 protected abstract void updateUsages(@NotNull Collection<String> newParamNames, @NotNull UsageInfo[] usages);
180 protected abstract PyFunction createNewFunction(@NotNull Collection<String> newParamNames);
183 protected final PyParameterList addParameters(@NotNull PyParameterList paramList, @NotNull Collection<String> newParameters) {
184 if (!newParameters.isEmpty()) {
185 final String commaSeparatedNames = StringUtil.join(newParameters, ", ");
186 final StringBuilder paramListText = new StringBuilder(paramList.getText());
187 paramListText.insert(1, commaSeparatedNames + (paramList.getParameters().length > 0 ? ", " : ""));
188 final PyParameterList newElement = myGenerator.createParameterList(LanguageLevel.forElement(myFunction), paramListText.toString());
189 return (PyParameterList)paramList.replace(newElement);
195 protected PyArgumentList addArguments(@NotNull PyArgumentList argList, @NotNull Collection<String> newArguments) {
196 if (!newArguments.isEmpty()) {
197 final String commaSeparatedNames = StringUtil.join(newArguments, ", ");
198 final StringBuilder argListText = new StringBuilder(argList.getText());
199 argListText.insert(1, commaSeparatedNames + (argList.getArguments().length > 0 ? ", " : ""));
200 final PyArgumentList newElement = myGenerator.createArgumentList(LanguageLevel.forElement(argList), argListText.toString());
201 return (PyArgumentList)argList.replace(newElement);
207 protected PyFunction insertFunction(@NotNull PyFunction newFunction, PyFile newFile) {
208 final PyFunction replacement;
209 if (mySourceFile == newFile) {
210 final PsiElement anchor;
211 anchor = PyPsiUtils.getParentRightBefore(myFunction, mySourceFile);
212 replacement = (PyFunction)mySourceFile.addAfter(newFunction, anchor);
215 replacement = (PyFunction)newFile.addAfter(newFunction, AddImportHelper.getFileInsertPosition(newFile));
221 protected AnalysisResult analyseScope(@NotNull ScopeOwner owner) {
222 final ControlFlow controlFlow = ControlFlowCache.getControlFlow(owner);
223 final AnalysisResult result = new AnalysisResult();
224 for (Instruction instruction : controlFlow.getInstructions()) {
225 if (instruction instanceof ReadWriteInstruction) {
226 final ReadWriteInstruction readWriteInstruction = (ReadWriteInstruction)instruction;
227 final PsiElement element = readWriteInstruction.getElement();
228 if (element == null) {
231 if (readWriteInstruction.getAccess().isReadAccess()) {
232 for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
233 if (resolved != null) {
234 if (isInitOrNewMethod(resolved)) {
235 resolved = ((PyFunction)resolved).getContainingClass();
237 if (isFromEnclosingScope(resolved)) {
238 result.readsFromEnclosingScope.add(element);
240 else if (!belongsToFunction(resolved)) {
241 myExternalReads.add(resolved);
243 if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf()) {
244 if (PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
245 result.readsOfSelfParameter.add(element);
247 else if (!PsiTreeUtil.isAncestor(myFunction, resolved, true)) {
248 result.readsOfSelfParametersFromEnclosingScope.add(element);
254 if (readWriteInstruction.getAccess().isWriteAccess() && element instanceof PyTargetExpression) {
255 for (PsiElement resolved : PyUtil.multiResolveTopPriority(element, myResolveContext)) {
256 if (resolved != null) {
257 if (element.getParent() instanceof PyNonlocalStatement && isFromEnclosingScope(resolved)) {
258 result.nonlocalWritesToEnclosingScope.add((PyTargetExpression)element);
260 if (resolved instanceof PyParameter && ((PyParameter)resolved).isSelf() &&
261 PsiTreeUtil.getParentOfType(resolved, PyFunction.class) == myFunction) {
262 result.writesToSelfParameter.add((PyTargetExpression)element);
272 private static boolean isInitOrNewMethod(@NotNull PsiElement elem) {
273 final PyFunction func = as(elem, PyFunction.class);
274 return func != null && (PyNames.INIT.equals(func.getName()) || PyNames.NEW.equals(func.getName()));
277 private boolean isFromEnclosingScope(@NotNull PsiElement element) {
278 return PyUtil.inSameFile(element, myFunction) &&
279 !belongsToFunction(element) &&
280 !(ScopeUtil.getScopeOwner(element) instanceof PsiFile);
283 protected static class AnalysisResult {
284 final List<PsiElement> readsFromEnclosingScope = new ArrayList<>();
285 final List<PyTargetExpression> nonlocalWritesToEnclosingScope = new ArrayList<>();
286 final List<PsiElement> readsOfSelfParametersFromEnclosingScope = new ArrayList<>();
287 final List<PsiElement> readsOfSelfParameter = new ArrayList<>();
288 // No one writes to "self", but handle this case too just to be sure
289 final List<PyTargetExpression> writesToSelfParameter = new ArrayList<>();