From 805753403c081e8949c9033fd3ea3d6ea091e5ac Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 5 Sep 2012 21:09:34 +0200 Subject: [PATCH 1/4] test for PY-7439 --- .../com/jetbrains/python/Py3ResolveTest.java | 3 +-- .../python/PyMultiFileResolveTest.java | 8 ++---- .../com/jetbrains/python/PyResolveTest.java | 3 +-- .../python/fixtures/PyResolveTestCase.java | 27 +++++++++++++++++-- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/python/testSrc/com/jetbrains/python/Py3ResolveTest.java b/python/testSrc/com/jetbrains/python/Py3ResolveTest.java index 6b54119aeb65..62d065bbe9c0 100644 --- a/python/testSrc/com/jetbrains/python/Py3ResolveTest.java +++ b/python/testSrc/com/jetbrains/python/Py3ResolveTest.java @@ -20,8 +20,7 @@ public class Py3ResolveTest extends PyResolveTestCase { @Override protected PsiElement doResolve() { myFixture.configureByFile("resolve/" + getTestName(false) + ".py"); - int offset = findMarkerOffset(myFixture.getFile()); - final PsiReference ref = myFixture.getFile().findReferenceAt(offset); + final PsiReference ref = findReferenceByMarker(myFixture.getFile()); return ref.resolve(); } diff --git a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java index d55cf9c6a58b..80b01c73a086 100644 --- a/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyMultiFileResolveTest.java @@ -446,9 +446,7 @@ public class PyMultiFileResolveTest extends PyResolveTestCase { } protected PsiElement doResolve(PsiFile psiFile) { - int offset = findMarkerOffset(psiFile); - final PsiPolyVariantReference ref = (PsiPolyVariantReference) psiFile.findReferenceAt(offset); - assertNotNull(" in test file not found", ref); + final PsiPolyVariantReference ref = findReferenceByMarker(psiFile); final PsiManagerImpl psiManager = (PsiManagerImpl)myFixture.getPsiManager(); psiManager.setAssertOnFileLoadingFilter(new VirtualFileFilter() { @Override @@ -476,9 +474,7 @@ public class PyMultiFileResolveTest extends PyResolveTestCase { private ResolveResult[] doMultiResolve() { PsiFile psiFile = prepareFile(); - int offset = findMarkerOffset(psiFile); - final PsiPolyVariantReference ref = (PsiPolyVariantReference)psiFile.findReferenceAt(offset); - assertNotNull(" in test file not found", ref); + final PsiPolyVariantReference ref = findReferenceByMarker(psiFile); return ref.multiResolve(false); } } diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 7cdfec822b1f..505fdc335b1f 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -19,8 +19,7 @@ public class PyResolveTest extends PyResolveTestCase { private PsiReference findReferenceByMarker() { myFixture.configureByFile("resolve/" + getTestName(false) + ".py"); - int offset = findMarkerOffset(myFixture.getFile()); - return myFixture.getFile().findReferenceAt(offset); + return findReferenceByMarker(myFixture.getFile()); } protected PsiElement resolve() { diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyResolveTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyResolveTestCase.java index e8a843d55d74..1d50c65836ea 100644 --- a/python/testSrc/com/jetbrains/python/fixtures/PyResolveTestCase.java +++ b/python/testSrc/com/jetbrains/python/fixtures/PyResolveTestCase.java @@ -11,6 +11,8 @@ import com.intellij.testFramework.TestDataFile; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; @@ -69,6 +71,19 @@ public abstract class PyResolveTestCase extends PyTestCase { catch (Exception e) { throw new RuntimeException(e); } + return assertResolveResult(element, aClass, name, containingFilePath); + } + + public static T assertResolveResult(PsiElement element, + Class aClass, + String name) { + return assertResolveResult(element, aClass, name, null); + } + + public static T assertResolveResult(PsiElement element, + Class aClass, + String name, + @Nullable String containingFilePath) { assertInstanceOf(element, aClass); assertEquals(name, ((PsiNamedElement) element).getName()); if (containingFilePath != null) { @@ -84,8 +99,8 @@ public abstract class PyResolveTestCase extends PyTestCase { return (T)element; } - protected int findMarkerOffset(final PsiFile psiFile) { - Document document = PsiDocumentManager.getInstance(myFixture.getProject()).getDocument(psiFile); + public static int findMarkerOffset(final PsiFile psiFile) { + Document document = PsiDocumentManager.getInstance(psiFile.getProject()).getDocument(psiFile); assert document != null; int offset = -1; for (int i=1; i in test file not found", ref); + return ref; + } } From 8b6b6182a948d3fa6504ecea61fc6a9d9ddcbe46 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Wed, 5 Sep 2012 23:12:57 +0400 Subject: [PATCH 2/4] Fixed NPE in PySkeletonRefresher.refreshSkeletons --- python/src/com/jetbrains/python/sdk/PySkeletonRefresher.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/sdk/PySkeletonRefresher.java b/python/src/com/jetbrains/python/sdk/PySkeletonRefresher.java index dc344d99a1a2..b38efdc25180 100644 --- a/python/src/com/jetbrains/python/sdk/PySkeletonRefresher.java +++ b/python/src/com/jetbrains/python/sdk/PySkeletonRefresher.java @@ -288,7 +288,9 @@ public class PySkeletonRefresher { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { - DaemonCodeAnalyzer.getInstance(myProject).restart(); + if (myProject != null) { + DaemonCodeAnalyzer.getInstance(myProject).restart(); + } } }); } From 61dd175ed2d7ebf05df10233caca8c9737f2ddb9 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 5 Sep 2012 23:36:16 +0200 Subject: [PATCH 3/4] one-line PyStatementList handles insertion correctly (PY-149) --- .../python/psi/PyElementGenerator.java | 3 ++ .../psi/impl/PyElementGeneratorImpl.java | 19 ++++++++++-- .../python/psi/impl/PyStatementListImpl.java | 14 +++++++++ .../jetbrains/python/PyStatementListTest.java | 30 +++++++++++++++++++ .../jetbrains/python/PythonAllTestsSuite.java | 3 +- 5 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 python/testSrc/com/jetbrains/python/PyStatementListTest.java diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java index 3f28b98312ff..e9276cd57c74 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java @@ -63,6 +63,9 @@ public abstract class PyElementGenerator { @NotNull public abstract T createFromText(LanguageLevel langLevel, Class aClass, final String text); + @NotNull + public abstract T createPhysicalFromText(LanguageLevel langLevel, Class aClass, final String text); + /** * Creates an arbitrary PSI element from text, by creating a bigger construction and then cutting the proper subelement. * Will produce all kinds of exceptions if the path or class would not match the PSI tree. diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java index f7209cf0cb9b..bcd753bdf801 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java @@ -42,11 +42,15 @@ public class PyElementGeneratorImpl extends PyElementGenerator { @Override public PsiFile createDummyFile(LanguageLevel langLevel, String contents) { + return createDummyFile(langLevel, contents, false); + } + + public PsiFile createDummyFile(LanguageLevel langLevel, String contents, boolean physical) { final PsiFileFactory factory = PsiFileFactory.getInstance(myProject); final String name = "dummy." + PythonFileType.INSTANCE.getDefaultExtension(); final LightVirtualFile virtualFile = new LightVirtualFile(name, PythonFileType.INSTANCE, contents); virtualFile.putUserData(LanguageLevel.KEY, langLevel); - final PsiFile psiFile = ((PsiFileFactoryImpl)factory).trySetupPsiForFile(virtualFile, PythonLanguage.getInstance(), false, true); + final PsiFile psiFile = ((PsiFileFactoryImpl)factory).trySetupPsiForFile(virtualFile, PythonLanguage.getInstance(), physical, true); assert psiFile != null; return psiFile; } @@ -233,6 +237,12 @@ public class PyElementGeneratorImpl extends PyElementGenerator { return createFromText(langLevel, aClass, text, FROM_ROOT); } + @NotNull + @Override + public T createPhysicalFromText(LanguageLevel langLevel, Class aClass, String text) { + return createFromText(langLevel, aClass, text, FROM_ROOT, true); + } + static int[] PATH_PARAMETER = {0, 3, 1}; public PyNamedParameter createParameter(@NotNull String name) { @@ -247,7 +257,12 @@ public class PyElementGeneratorImpl extends PyElementGenerator { @NotNull public T createFromText(LanguageLevel langLevel, Class aClass, final String text, final int[] path) { - PsiElement ret = createDummyFile(langLevel, text); + return createFromText(langLevel, aClass, text, path, false); + } + + @NotNull + public T createFromText(LanguageLevel langLevel, Class aClass, final String text, final int[] path, boolean physical) { + PsiElement ret = createDummyFile(langLevel, text, physical); for (int skip : path) { if (ret != null) { ret = ret.getFirstChild(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyStatementListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyStatementListImpl.java index fb4a56ed0014..7d70adc1478a 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyStatementListImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyStatementListImpl.java @@ -1,6 +1,8 @@ package com.jetbrains.python.psi.impl; +import com.intellij.lang.ASTFactory; import com.intellij.lang.ASTNode; +import com.intellij.psi.TokenType; import com.jetbrains.python.PythonDialectsTokenSetProvider; import com.jetbrains.python.psi.PyElementVisitor; import com.jetbrains.python.psi.PyStatement; @@ -22,4 +24,16 @@ public class PyStatementListImpl extends PyElementImpl implements PyStatementLis public PyStatement[] getStatements() { return childrenToPsi(PythonDialectsTokenSetProvider.INSTANCE.getStatementTokens(), PyStatement.EMPTY_ARRAY); } + + @Override + public ASTNode addInternal(ASTNode first, ASTNode last, ASTNode anchor, Boolean before) { + if (first.getPsi() instanceof PyStatement && getStatements().length == 1) { + ASTNode treePrev = getNode().getTreePrev(); + if (treePrev != null && treePrev.getElementType() == TokenType.WHITE_SPACE && !treePrev.textContains('\n')) { + ASTNode lineBreak = ASTFactory.whitespace("\n"); + treePrev.getTreeParent().replaceChild(treePrev, lineBreak); + } + } + return super.addInternal(first, last, anchor, before); + } } diff --git a/python/testSrc/com/jetbrains/python/PyStatementListTest.java b/python/testSrc/com/jetbrains/python/PyStatementListTest.java new file mode 100644 index 000000000000..3404eee421ce --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyStatementListTest.java @@ -0,0 +1,30 @@ +package com.jetbrains.python; + +import com.intellij.openapi.command.WriteCommandAction; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyStatementList; + +/** + * @author yole + */ +public class PyStatementListTest extends PyTestCase { + public void testOneLineList() { + PyElementGenerator generator = PyElementGenerator.getInstance(myFixture.getProject()); + PyFunction function = generator.createPhysicalFromText(LanguageLevel.PYTHON27, PyFunction.class, "def foo(): print 1"); + PyFunction function2 = generator.createPhysicalFromText(LanguageLevel.PYTHON27, PyFunction.class, "def foo(): print 2"); + final PyStatementList list1 = function.getStatementList(); + final PyStatementList list2 = function2.getStatementList(); + + new WriteCommandAction.Simple(myFixture.getProject()) { + @Override + protected void run() throws Throwable { + list1.add(list2.getStatements()[0]); + } + }.execute(); + + assertEquals("def foo():\n print 1\n print 2", function.getText()); + } +} diff --git a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java index a7dc71ab0544..cd0523dc0efb 100644 --- a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java +++ b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java @@ -99,7 +99,8 @@ public class PythonAllTestsSuite { PyPropertyAccessInspectionTest.class, Jinja2ParserTest.class, DjangoTemplateParserTest.class, - PyJoinLinesTest.class + PyJoinLinesTest.class, + PyStatementListTest.class }; public static TestSuite suite() { From bdbc9cc5dba81d2ac3d1bfb88e2b65288a0df5c0 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Thu, 6 Sep 2012 18:11:19 +0400 Subject: [PATCH 4/4] npe fix --- .../codeInsight/intentions/SpecifyTypeInDocstringIntention.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java index 61a621ef780d..54a20eeceb26 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java @@ -50,7 +50,7 @@ public class SpecifyTypeInDocstringIntention implements IntentionAction { public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset() - 1); - if (elementAt != null && !(elementAt.getNode().getElementType() == PyTokenTypes.IDENTIFIER)) + if (elementAt != null && elementAt.getNode() != null && !(elementAt.getNode().getElementType() == PyTokenTypes.IDENTIFIER)) elementAt = file.findElementAt(editor.getCaretModel().getOffset()); PyCallExpression callExpression = PsiTreeUtil.getParentOfType(elementAt, PyCallExpression.class);