From 2c8ff937db6cd0a815ce76a5c5e10fd4c793569e Mon Sep 17 00:00:00 2001 From: Aleksei Kniazev Date: Fri, 23 Aug 2019 12:16:21 +0300 Subject: [PATCH] IDEA-CR-51700: go to definition/implementation on file should lead to py files instead of pyi (PY-35129, PY-32345) GitOrigin-RevId: 0390bc3103ff22903e96d1a669cd4a6aca0980e9 --- .../psi/impl/PyGotoDeclarationHandler.java | 11 +++- .../python/psi/resolve/PyResolveImportUtil.kt | 4 ++ .../psi/search/PyDefinitionsSearch.java | 8 +++ .../goToDeclarationForDirectory/test.py | 1 + .../testData/navigation/onPyiFile/source.py | 2 + .../testData/navigation/onPyiFile/source.pyi | 1 + python/testData/navigation/onPyiFile/test.py | 1 + .../com/jetbrains/python/PyNavigationTest.kt | 54 +++++++++++++++++++ 8 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 python/testData/navigation/goToDeclarationForDirectory/test.py create mode 100644 python/testData/navigation/onPyiFile/source.py create mode 100644 python/testData/navigation/onPyiFile/source.pyi create mode 100644 python/testData/navigation/onPyiFile/test.py create mode 100644 python/testSrc/com/jetbrains/python/PyNavigationTest.kt diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyGotoDeclarationHandler.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyGotoDeclarationHandler.java index 17547733fc38..0f622d17be8d 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyGotoDeclarationHandler.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyGotoDeclarationHandler.java @@ -19,10 +19,14 @@ import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandlerBase; import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; +import com.intellij.util.ObjectUtils; import com.jetbrains.python.PyUserInitiatedResolvableReference; +import com.jetbrains.python.psi.PyElement; import com.jetbrains.python.psi.PyReferenceOwner; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.types.TypeEvalContext; +import com.jetbrains.python.pyi.PyiFile; +import com.jetbrains.python.pyi.PyiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -55,7 +59,12 @@ public final class PyGotoDeclarationHandler extends GotoDeclarationHandlerBase { referenceOwner = (PyReferenceOwner)parent; //Reference expression may be parent of IDENTIFIER } if (referenceOwner != null) { - return referenceOwner.getReference(context).resolve(); + final PsiElement resolved = referenceOwner.getReference(context).resolve(); + if (resolved instanceof PyiFile) { + final PsiElement original = PyiUtil.getOriginalElement(((PyElement)resolved)); + return ObjectUtils.chooseNotNull(original, resolved); + } + return resolved; } // If element is not ref owner, it still may have provided references, lets find some final PsiElement element = findProvidedReferenceAndResolve(sourceElement); diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt index 62075f55d7cf..6ff8c933567b 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/PyResolveImportUtil.kt @@ -241,6 +241,7 @@ private fun resultsFromRoots(name: QualifiedName, context: PyQualifiedNameResolv val sdk = context.effectiveSdk val module = context.module val footholdFile = context.footholdFile + val withoutStubs = context.withoutStubs val visitor = RootVisitor { root, module, sdk, isModuleSource -> val results = if (isModuleSource) moduleResults else sdkResults @@ -250,6 +251,9 @@ private fun resultsFromRoots(name: QualifiedName, context: PyQualifiedNameResolv effectiveSdk != null && PyTypeShed.isInside(root) && !PyTypeShed.maySearchForStubInRoot(name, root, effectiveSdk)) { return@RootVisitor true } + if (withoutStubs && (PyTypeShed.isInside(root) || isInStubPackage(PsiManager.getInstance(context.project).findDirectory(root)!!))) { + return@RootVisitor true + } results.addAll(resolveInRoot(name, root, context)) if (isAcceptRootAsTopLevelPackage(context) && name.matchesPrefix( QualifiedName.fromDottedString(root.name))) { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java b/python/python-psi-impl/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java index f0ea7d55cc86..a1a21e2c5fd1 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/search/PyDefinitionsSearch.java @@ -10,6 +10,8 @@ import com.jetbrains.python.psi.PyAssignmentStatement; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyTargetExpression; +import com.jetbrains.python.pyi.PyiFile; +import com.jetbrains.python.pyi.PyiUtil; import org.jetbrains.annotations.NotNull; /** @@ -35,6 +37,12 @@ public class PyDefinitionsSearch implements QueryExecutor PyiUtil.getOriginalElement((PyiFile)queryParameters)); + if (originalElement != null) { + consumer.process(originalElement); + } + } return true; } } diff --git a/python/testData/navigation/goToDeclarationForDirectory/test.py b/python/testData/navigation/goToDeclarationForDirectory/test.py new file mode 100644 index 000000000000..188808d70dd1 --- /dev/null +++ b/python/testData/navigation/goToDeclarationForDirectory/test.py @@ -0,0 +1 @@ +import collections \ No newline at end of file diff --git a/python/testData/navigation/onPyiFile/source.py b/python/testData/navigation/onPyiFile/source.py new file mode 100644 index 000000000000..62425f098cfb --- /dev/null +++ b/python/testData/navigation/onPyiFile/source.py @@ -0,0 +1,2 @@ +def foo(): + return "bar" \ No newline at end of file diff --git a/python/testData/navigation/onPyiFile/source.pyi b/python/testData/navigation/onPyiFile/source.pyi new file mode 100644 index 000000000000..17e4ad1109c3 --- /dev/null +++ b/python/testData/navigation/onPyiFile/source.pyi @@ -0,0 +1 @@ +def foo() -> str: ... \ No newline at end of file diff --git a/python/testData/navigation/onPyiFile/test.py b/python/testData/navigation/onPyiFile/test.py new file mode 100644 index 000000000000..03d1e1317b6f --- /dev/null +++ b/python/testData/navigation/onPyiFile/test.py @@ -0,0 +1 @@ +from source import foo \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyNavigationTest.kt b/python/testSrc/com/jetbrains/python/PyNavigationTest.kt new file mode 100644 index 000000000000..30e08e85a816 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyNavigationTest.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python + +import com.intellij.psi.PsiElement +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.CodeInsightTestUtil +import com.jetbrains.python.fixtures.PyTestCase +import com.jetbrains.python.psi.PyFile +import com.jetbrains.python.psi.impl.PyGotoDeclarationHandler +import com.jetbrains.python.pyi.PyiFile + +class PyNavigationTest : PyTestCase() { + + // PY-35129 + fun testGoToDeclarationOnPyiFile() { + configureByDir("onPyiFile") + val target = PyGotoDeclarationHandler().getGotoDeclarationTarget(elementAtCaret, myFixture.editor) + checkPyNotPyi(target) + } + + // PY-35129 + fun testGoToImplementationOnPyiFile() { + configureByDir("onPyiFile") + val gotoData = CodeInsightTestUtil.gotoImplementation(myFixture.editor, myFixture.file) + assertSize(1, gotoData.targets) + checkPyNotPyi(gotoData.targets[0]) + } + + // PY-35129 + fun testGoToDeclarationForDirectory() { + configureByDir(getTestName(true)) + val target = PyGotoDeclarationHandler().getGotoDeclarationTarget(elementAtCaret, myFixture.editor) + checkPyNotPyi(target) + } + + private fun configureByDir(dirName: String) { + myFixture.copyDirectoryToProject(dirName, "") + myFixture.configureByFile("test.py") + assertTrue(myFixture.elementAtCaret is PyiFile) + } + + private fun checkPyNotPyi(file: PsiElement?) { + assertTrue(file is PyFile) + assertTrue(file !is PyiFile) + } + + override fun getTestDataPath(): String { + return super.getTestDataPath() + "/navigation" + } + + override fun getProjectDescriptor(): LightProjectDescriptor? = ourPy3Descriptor + + +} \ No newline at end of file