From d16d1c45cf0b6a6232f3758e7615258c01621206 Mon Sep 17 00:00:00 2001 From: Oleg Shpynov Date: Wed, 27 Jan 2010 17:14:16 +0300 Subject: [PATCH] PyCodeFragmentBuilder with imports support and tests --- .../codeFragment/PyCodeFragmentBuilder.java | 31 ++-- .../psi/impl/PyImportStatementNavigator.java | 26 ++++ .../codeInsight/codefragment/empty.test | 8 + .../codeInsight/codefragment/expression.test | 14 ++ .../codeInsight/codefragment/importafter.test | 9 ++ .../codefragment/importbefore.test | 9 ++ .../codefragment/importbeforeuseinside.test | 10 ++ .../codefragment/importinsideuseafter.test | 9 ++ .../codeInsight/codefragment/out.test | 9 ++ .../codeInsight/codefragment/parameters.test | 10 ++ .../codeInsight/codefragment/simple.test | 9 ++ .../codeInsight/codefragment/variables.test | 13 ++ .../codefragment/variablesemptyin.test | 7 + .../codefragment/variablesemptyout.test | 8 + .../refactoring/PyCodeFragmentTest.java | 137 ++++++++++++++++++ 15 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java create mode 100644 python/testData/codeInsight/codefragment/empty.test create mode 100644 python/testData/codeInsight/codefragment/expression.test create mode 100644 python/testData/codeInsight/codefragment/importafter.test create mode 100644 python/testData/codeInsight/codefragment/importbefore.test create mode 100644 python/testData/codeInsight/codefragment/importbeforeuseinside.test create mode 100644 python/testData/codeInsight/codefragment/importinsideuseafter.test create mode 100644 python/testData/codeInsight/codefragment/out.test create mode 100644 python/testData/codeInsight/codefragment/parameters.test create mode 100644 python/testData/codeInsight/codefragment/simple.test create mode 100644 python/testData/codeInsight/codefragment/variables.test create mode 100644 python/testData/codeInsight/codefragment/variablesemptyin.test create mode 100644 python/testData/codeInsight/codefragment/variablesemptyout.test create mode 100644 python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java index 0d058239e765..9cdc99daf51d 100644 --- a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java @@ -7,6 +7,7 @@ import com.intellij.psi.ResolveResult; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyImportStatementNavigator; import java.util.*; @@ -31,16 +32,27 @@ public class PyCodeFragmentBuilder extends PyRecursiveElementVisitor { @Override public void visitPyTargetExpression(final PyTargetExpression node) { - visitDeclaration(node); + processDeclaration(node); } @Override public void visitPyNamedParameter(final PyNamedParameter node) { - visitDeclaration(node); + processDeclaration(node); } @Override public void visitPyReferenceExpression(final PyReferenceExpression element) { + // Python PSI makes us to visit qualifier manually + final PyExpression qualifier = element.getQualifier(); + if (qualifier != null){ + qualifier.accept(this); + } + // Process import references + if (PyImportStatementNavigator.getImportStatementByReference(element) != null){ + processDeclaration(element); + return; + } + final Position position = CodeFragmentUtil.getPosition(element, startOffset, endOffset); final String name = element.getName(); @@ -68,6 +80,12 @@ public class PyCodeFragmentBuilder extends PyRecursiveElementVisitor { } for (ResolveResult result : element.multiResolve(false)) { final PsiElement declaration = result.getElement(); + // Handle resolve via import statement + if (declaration instanceof PyFile && modifiedInsideMap.containsKey(name)){ + outElements.add(name); + break; + } + // Ignore declarations out of scope if (declaration == null || !PsiTreeUtil.isAncestor(myOwner, declaration, false)){ continue; } @@ -98,13 +116,11 @@ public class PyCodeFragmentBuilder extends PyRecursiveElementVisitor { } } - private void visitDeclaration(final PyElement element) { + private void processDeclaration(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 list = modifiedInsideMap.get(name); if (list == null) { @@ -113,10 +129,5 @@ public class PyCodeFragmentBuilder extends PyRecursiveElementVisitor { } list.add(element); } - - // if name is already in out parameters - if (inElements.contains(name)) { - return; - } } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java b/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java new file mode 100644 index 000000000000..eb9ab0d666e6 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyImportStatementNavigator.java @@ -0,0 +1,26 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.PyImportElement; +import com.jetbrains.python.psi.PyImportStatement; +import org.jetbrains.annotations.Nullable; + +/** + * @author oleg + */ +public class PyImportStatementNavigator { + @Nullable + public static PyImportStatement getImportStatementByReference(final PsiElement element){ + final PyImportStatement statement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class, false); + if (statement == null){ + return null; + } + for (PyImportElement importElement : statement.getImportElements()) { + if (element == importElement.getImportReference()){ + return statement; + } + } + return null; + } +} diff --git a/python/testData/codeInsight/codefragment/empty.test b/python/testData/codeInsight/codefragment/empty.test new file mode 100644 index 000000000000..159ec3a6064b --- /dev/null +++ b/python/testData/codeInsight/codefragment/empty.test @@ -0,0 +1,8 @@ +import foo + +print("Foo was imported") + +foo.do_something + +In: +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/expression.test b/python/testData/codeInsight/codefragment/expression.test new file mode 100644 index 000000000000..011378441b02 --- /dev/null +++ b/python/testData/codeInsight/codefragment/expression.test @@ -0,0 +1,14 @@ +a = 12 +if some_cond: + b = 1 + +c = + +foo(boo(a) + 123 * b) + + + +In: +a +b +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/importafter.test b/python/testData/codeInsight/codefragment/importafter.test new file mode 100644 index 000000000000..56f807b1c41c --- /dev/null +++ b/python/testData/codeInsight/codefragment/importafter.test @@ -0,0 +1,9 @@ +print("start") + +print("code") + +import foo +foo.bar + +In: +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/importbefore.test b/python/testData/codeInsight/codefragment/importbefore.test new file mode 100644 index 000000000000..14314921f590 --- /dev/null +++ b/python/testData/codeInsight/codefragment/importbefore.test @@ -0,0 +1,9 @@ +print("start") +import foo + +print("code") + +foo.bar + +In: +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/importbeforeuseinside.test b/python/testData/codeInsight/codefragment/importbeforeuseinside.test new file mode 100644 index 000000000000..7e095473b9a3 --- /dev/null +++ b/python/testData/codeInsight/codefragment/importbeforeuseinside.test @@ -0,0 +1,10 @@ +print("start") +import foo + +print(foo) + +foo.bar + +In: +foo +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/importinsideuseafter.test b/python/testData/codeInsight/codefragment/importinsideuseafter.test new file mode 100644 index 000000000000..cd87f6ac6b46 --- /dev/null +++ b/python/testData/codeInsight/codefragment/importinsideuseafter.test @@ -0,0 +1,9 @@ +print("start") + +import foo + +foo.bar + +In: +Out: +foo \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/out.test b/python/testData/codeInsight/codefragment/out.test new file mode 100644 index 000000000000..7461a87dfe49 --- /dev/null +++ b/python/testData/codeInsight/codefragment/out.test @@ -0,0 +1,9 @@ +print("start") + +aaa = 123 + +print(aaa) + +In: +Out: +aaa \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/parameters.test b/python/testData/codeInsight/codefragment/parameters.test new file mode 100644 index 000000000000..672a8c33e4ab --- /dev/null +++ b/python/testData/codeInsight/codefragment/parameters.test @@ -0,0 +1,10 @@ +def plus(dddd, eeeee): + + dddd + eeeee * 123 + + + +In: +dddd +eeeee +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/simple.test b/python/testData/codeInsight/codefragment/simple.test new file mode 100644 index 000000000000..2ff4f6bc1606 --- /dev/null +++ b/python/testData/codeInsight/codefragment/simple.test @@ -0,0 +1,9 @@ +aaa = 123 + +print(aaa) + +print("Hello") + +In: +aaa +Out: diff --git a/python/testData/codeInsight/codefragment/variables.test b/python/testData/codeInsight/codefragment/variables.test new file mode 100644 index 000000000000..6ad44ee145e7 --- /dev/null +++ b/python/testData/codeInsight/codefragment/variables.test @@ -0,0 +1,13 @@ +aaa = 12 + +bbb = aaa +aaa = bbb + 123 + +bbb +aaa + +In: +aaa +Out: +aaa +bbb \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/variablesemptyin.test b/python/testData/codeInsight/codefragment/variablesemptyin.test new file mode 100644 index 000000000000..a7e7bf39cb0f --- /dev/null +++ b/python/testData/codeInsight/codefragment/variablesemptyin.test @@ -0,0 +1,7 @@ +aaa = 12 + +aaa = 123 + + +In: +Out: \ No newline at end of file diff --git a/python/testData/codeInsight/codefragment/variablesemptyout.test b/python/testData/codeInsight/codefragment/variablesemptyout.test new file mode 100644 index 000000000000..fda2a07391a7 --- /dev/null +++ b/python/testData/codeInsight/codefragment/variablesemptyout.test @@ -0,0 +1,8 @@ + +aaa = 12 + +aaa = 12 +aaa + +In: +Out: \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java new file mode 100644 index 000000000000..ff3ee44188dc --- /dev/null +++ b/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java @@ -0,0 +1,137 @@ +package com.jetbrains.python.refactoring; + +import com.intellij.codeInsight.codeFragment.CodeFragment; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PythonTestUtil; +import com.jetbrains.python.codeInsight.codeFragment.PyCodeFragmentUtil; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.fixtures.LightMarkedTestCase; +import com.jetbrains.python.psi.PyFile; + +import java.io.File; +import java.util.TreeSet; + +/** + * @author oleg + */ +public class PyCodeFragmentTest extends LightMarkedTestCase { + public String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/codeInsight/codefragment/"; + } + + final private String BEGIN_MARKER = ""; + final private String END_MARKER = ""; + final private String RESULT_MARKER = ""; + + private void doTest(Pair... files2Create) throws Exception { + final String testName = getTestName(false).toLowerCase(); + final String fullPath = getTestDataPath() + testName + ".test"; + + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/')); + String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n"); + + final int beginMarker = fileText.indexOf(BEGIN_MARKER); + final int endMarker = fileText.indexOf(END_MARKER); + final int resultMarker = fileText.indexOf(RESULT_MARKER); + assertTrue(beginMarker != -1); + assertTrue(endMarker != -1); + assertTrue(resultMarker != -1); + + final StringBuilder builder = new StringBuilder(); + builder.append(fileText.substring(0, beginMarker)); + builder.append(fileText.substring(beginMarker + BEGIN_MARKER.length(), endMarker)); + builder.append((fileText.substring(endMarker + END_MARKER.length(), resultMarker))); + + final String result = fileText.substring(resultMarker + RESULT_MARKER.length()); + + // Create additional files + for (Pair pair : files2Create) { + myFixture.addFileToProject(pair.first, pair.second); + } + + final PyFile file = (PyFile)myFixture.addFileToProject(testName + ".py", builder.toString()); + check(file, beginMarker, endMarker, result); + } + + private void check(final PyFile myFile, final int beginMarker, final int endMarker, final String result) { + final PsiElement startElement = myFile.findElementAt(beginMarker); + final PsiElement endElement = myFile.findElementAt(endMarker - BEGIN_MARKER.length()); + PsiElement context = PsiTreeUtil.findCommonParent(startElement, endElement); + if (!(context instanceof ScopeOwner)) { + context = PsiTreeUtil.getParentOfType(context, ScopeOwner.class); + } + final StringBuffer buffer = new StringBuffer(); + final CodeFragment fragment = PyCodeFragmentUtil.createCodeFragment((ScopeOwner)context, startElement, endElement); + if (fragment.isReturnInstructonInside()) { + buffer.append("Return instruction inside found").append("\n"); + } + buffer.append("In:\n"); + for (String inputVariable : new TreeSet(fragment.getInputVariables())) { + buffer.append(inputVariable).append('\n'); + } + buffer.append("Out:\n"); + for (String outputVariable : new TreeSet(fragment.getOutputVariables())) { + buffer.append(outputVariable).append('\n'); + } + + assertEquals(result.trim(), buffer.toString().trim()); + } + + public void testImportBefore() throws Exception { + doTest(Pair.create("foo.py", "")); + } + + public void testImportBeforeUseInside() throws Exception { + doTest(Pair.create("foo.py", "")); + } + + public void testImportInsideUseAfter() throws Exception { + doTest(Pair.create("foo.py", "")); + } + + public void testImportAfter() throws Exception { + doTest(Pair.create("foo.py", "")); + } + + + public void testSimple() throws Exception { + doTest(); + } + + public void testEmpty() throws Exception { + doTest(); + } + + public void testOut() throws Exception { + doTest(); + } + + + public void testExpression() throws Exception { + doTest(); + } + + public void testParameters() throws Exception { + doTest(); + } + + public void testVariables() throws Exception { + doTest(); + } + + public void testVariablesEmptyOut() throws Exception { + doTest(); + } + + public void testVariablesEmptyIn() throws Exception { + doTest(); + } + +} +