Revert "Revert "First step of extract method: CodeFragment""

This reverts commit a6ee7cccd3b9162b007a6d2aca4b086ceceb39ef.
This commit is contained in:
Oleg Shpynov
2010-01-27 11:18:14 +03:00
parent d2107bd3b4
commit 0a8cdc39ac
10 changed files with 307 additions and 7 deletions
@@ -0,0 +1,112 @@
package com.jetbrains.python.codeInsight.codeFragment;
import com.intellij.codeInsight.codeFragment.CodeFragmentUtil;
import com.intellij.codeInsight.codeFragment.Position;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveResult;
import com.jetbrains.python.psi.*;
import java.util.*;
/**
* @author oleg
*/
public class PyCodeFragmentBuilder extends PyRecursiveElementVisitor {
final Map<String, List<PyElement>> modifiedInsideMap = new HashMap<String, List<PyElement>>();
final Set<String> inElements = new HashSet<String>();
final Set<String> outElements = new HashSet<String>();
private final int startOffset;
private final int endOffset;
public PyCodeFragmentBuilder(int start, int end) {
startOffset = start;
endOffset = end;
}
@Override
public void visitPyTargetExpression(final PyTargetExpression node) {
visitDeclaration(node);
}
@Override
public void visitPyNamedParameter(final PyNamedParameter node) {
visitDeclaration(node);
}
@Override
public void visitPyReferenceExpression(final PyReferenceExpression element) {
final Position position = CodeFragmentUtil.getPosition(element, startOffset, endOffset);
final String name = element.getName();
// Collect in variables
if (position == Position.INSIDE) {
for (ResolveResult result : element.multiResolve(false)) {
final PsiElement declaration = result.getElement();
final Position pos = CodeFragmentUtil.getPosition(declaration, startOffset, endOffset);
// If declaration is before add it to input
if (pos == Position.BEFORE) {
inElements.add(name);
break;
}
}
}
// Collect out variables
if (position == Position.AFTER) {
// if name is already in out parameters
if (outElements.contains(name)) {
return;
}
for (ResolveResult result : element.multiResolve(false)) {
final PsiElement declaration = result.getElement();
final Position pos = CodeFragmentUtil.getPosition(declaration, startOffset, endOffset);
// If declaration is inside
if (pos == Position.INSIDE) {
outElements.add(name);
break;
}
// If declaration is before we look for modifications inside
if (pos == Position.BEFORE) {
final List<PyElement> list = modifiedInsideMap.get(name);
boolean modificationSeen = false;
if (list != null) {
for (PyElement modification : list) {
if (modification.getReference().isReferenceTo(declaration)) {
outElements.add(name);
modificationSeen = true;
break;
}
}
if (modificationSeen) {
break;
}
}
}
}
}
}
private void visitDeclaration(final PyElement element) {
final Position position = CodeFragmentUtil.getPosition(element, startOffset, endOffset);
final String name = element.getName();
// Collect in variables
if (position == Position.INSIDE) {
// Add modification inside
List<PyElement> list = modifiedInsideMap.get(name);
if (list == null) {
list = new ArrayList<PyElement>();
modifiedInsideMap.put(name, list);
}
list.add(element);
}
// if name is already in out parameters
if (inElements.contains(name)) {
return;
}
}
}
@@ -0,0 +1,113 @@
package com.jetbrains.python.codeInsight.codeFragment;
import com.intellij.codeInsight.codeFragment.CannotCreateCodeFragmentException;
import com.intellij.codeInsight.codeFragment.CodeFragment;
import com.intellij.codeInsight.codeFragment.CodeFragmentUtil;
import com.intellij.codeInsight.codeFragment.Position;
import com.intellij.codeInsight.controlflow.ConditionalInstruction;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.HashSet;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.controlflow.WriteInstruction;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBinaryExpressionNavigator;
import com.jetbrains.python.psi.impl.PyForStatementNavigator;
import org.jetbrains.annotations.NotNull;
/**
* @author oleg
*/
public class PyCodeFragmentUtil {
private PyCodeFragmentUtil() {
}
public static CodeFragment createCodeFragment(@NotNull final ScopeOwner owner,
@NotNull final PsiElement startInScope,
@NotNull final PsiElement endInScope) throws CannotCreateCodeFragmentException {
final int start = startInScope.getTextOffset();
final int end = endInScope.getTextOffset() + endInScope.getTextLength();
// Check for class or function inside code fragment
owner.acceptChildren(new PyRecursiveElementVisitor(){
@Override
public void visitPyClass(final PyClass node) {
if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){
throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.class.declaration.inside"));
}
}
@Override
public void visitPyFunction(final PyFunction node) {
if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){
throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.class.declaration.inside"));
}
}
});
// Control flow inspection
final HashSet<Instruction> outerInstructions = new HashSet<Instruction>();
boolean returnInstructionInside = false;
final Instruction[] flow = owner.getControlFlow().getInstructions();
for (Instruction instruction : flow) {
final PsiElement element = instruction.getElement();
if (element!=null && CodeFragmentUtil.elementFit(element, start, end)){
if (element instanceof PyReturnStatement){
returnInstructionInside = true;
}
if (element instanceof PyBreakStatement && !CodeFragmentUtil.elementFit(((PyBreakStatement) element).getLoopStatement(), start, end)){
throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.no.corresponding.loop.for.break"));
}
if (element instanceof PyContinueStatement && !CodeFragmentUtil.elementFit(((PyContinueStatement) element).getLoopStatement(), start, end)){
throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.no.corresponding.loop.for.continue"));
}
for (Instruction next : instruction.allSucc()) {
// Ignore conditional instruction
if (next instanceof ConditionalInstruction){
continue;
}
final PsiElement nextElement = next.getElement();
// Ignore binary operations control flow
if (nextElement != null && PyBinaryExpressionNavigator.getBinaryExpressionByOperand(nextElement) != null){
continue;
}
// We ignore except blocks
if (nextElement instanceof PyExceptPart){
continue;
}
if (!CodeFragmentUtil.elementFit(nextElement, start, end)){
outerInstructions.add(next);
}
}
}
}
// If we see more than 1 outer instruction, controlflow is interrupted
if (outerInstructions.size() > 2){
throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted"));
}
if (outerInstructions.size() == 2){
boolean errorFound = true;
for (Instruction outerInstruction : outerInstructions) {
// Here we check control flow when for statement content is beeing extracted
if (outerInstruction instanceof WriteInstruction &&
PyForStatementNavigator.getPyForStatementByIterable(outerInstruction.getElement())!=null){
errorFound = false;
break;
}
}
if (errorFound){
throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted"));
}
}
// Building code fragment
final PyCodeFragmentBuilder builder = new PyCodeFragmentBuilder(start, end);
owner.acceptChildren(builder);
return new CodeFragment(builder.inElements, builder.outElements, returnInstructionInside);
}
}
@@ -253,7 +253,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
final Instruction nextInstruction = new InstructionImpl(myBuilder, node);
myBuilder.addNode(nextInstruction);
myBuilder.checkPending(nextInstruction);
final PyLoopStatement loop = node.getLoop();
final PyLoopStatement loop = node.getLoopStatement();
if (loop != null) {
final Instruction instruction = myBuilder.findInstructionByElement(loop);
if (instruction != null) {
@@ -16,6 +16,7 @@
package com.jetbrains.python.psi;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.Nullable;
/**
@@ -29,6 +30,8 @@ public interface PyBinaryExpression extends PyExpression {
PyExpression getLeftExpression();
@Nullable PyExpression getRightExpression();
PyElementType getOperator();
@Nullable
PsiElement getPsiOperator();
boolean isOperator(String chars);
PyExpression getOppositeExpression(PyExpression expression)
@@ -27,5 +27,5 @@ import org.jetbrains.annotations.Nullable;
*/
public interface PyContinueStatement extends PyStatement {
@Nullable
PyLoopStatement getLoop();
PyLoopStatement getLoopStatement();
}
@@ -17,6 +17,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
@@ -24,6 +25,7 @@ import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Created by IntelliJ IDEA.
@@ -55,11 +57,15 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp
@PsiCached
public PyElementType getOperator() {
final PsiElement psiOperator = getPsiOperator();
return psiOperator != null ? (PyElementType)psiOperator.getNode().getElementType() : null;
}
@Nullable
public PsiElement getPsiOperator() {
ASTNode node = getNode();
if (node != null) {
final ASTNode child = node.findChildByType(PyElementTypes.BINARY_OPS);
if (child != null) return (PyElementType)child.getElementType();
}
final ASTNode child = node.findChildByType(PyElementTypes.BINARY_OPS);
if (child != null) return child.getPsi();
return null;
}
@@ -0,0 +1,23 @@
package com.jetbrains.python.psi.impl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.PyBinaryExpression;
import org.jetbrains.annotations.Nullable;
/**
* @author oleg
*/
public class PyBinaryExpressionNavigator {
@Nullable
public static PyBinaryExpression getBinaryExpressionByOperand(final PsiElement element) {
final PyBinaryExpression expression = PsiTreeUtil.getParentOfType(element, PyBinaryExpression.class, false);
if (expression == null){
return null;
}
if (expression.getPsiOperator() == element){
return expression;
}
return null;
}
}
@@ -40,7 +40,7 @@ public class PyContinueStatementImpl extends PyElementImpl implements PyContinue
}
@Nullable
public PyLoopStatement getLoop() {
public PyLoopStatement getLoopStatement() {
return PsiTreeUtil.getParentOfType(this, PyLoopStatement.class);
}
}
@@ -0,0 +1,38 @@
package com.jetbrains.python.refactoring;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.refactoring.RefactoringActionHandler;
import org.jetbrains.annotations.NotNull;
/**
* @author oleg
*/
public class PyExtractMethodHandler implements RefactoringActionHandler {
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, final DataContext dataContext) {
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
// select editor text fragment
if (!editor.getSelectionModel().hasSelection()) {
editor.getSelectionModel().selectLineAtCaret();
}
invokeOnEditor(project, editor, file);
}
public void invoke(@NotNull final Project project, @NotNull final PsiElement[] elements, final DataContext dataContext) {
}
private void invokeOnEditor(final Project project, final Editor editor, final PsiFile file) {
final SelectionModel selectionModel = editor.getSelectionModel();
final int start = selectionModel.getSelectionStart();
final int end = selectionModel.getSelectionEnd();
}
}
@@ -39,4 +39,9 @@ public class RefactoringProvider extends DefaultRefactoringSupportProvider {
public RefactoringActionHandler getExtractSuperClassHandler() {
return new PyExtractSuperclassHandler();
}
@Override
public RefactoringActionHandler getExtractMethodHandler() {
return new PyExtractMethodHandler();
}
}