mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
intention actions to switch between absolute and relative imports (PY-37858)
GitOrigin-RevId: 8c3c1caeba64ff678c8f1dafdd0fc9a44af3873e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fccb01ec87
commit
adbbcef28a
+1
@@ -0,0 +1 @@
|
||||
from <spot>.three</spot> import foo
|
||||
+1
@@ -0,0 +1 @@
|
||||
from <spot>one.two.three</spot> import foo
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<span>
|
||||
This intention replaces absolute <code>from</code> import with a relative one.
|
||||
</span>
|
||||
</body>
|
||||
</html>
|
||||
+1
@@ -0,0 +1 @@
|
||||
from <spot>one.two.three</spot> import foo
|
||||
+1
@@ -0,0 +1 @@
|
||||
from <spot>.three</spot> import foo
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
<span>
|
||||
This intention replaces relative <code>from</code> import with an absolute one.
|
||||
</span>
|
||||
</body>
|
||||
</html>
|
||||
@@ -217,6 +217,16 @@
|
||||
<category>Python</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.jetbrains.python.codeInsight.intentions.PyAbsoluteToRelativeImportIntention</className>
|
||||
<category>Python</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.jetbrains.python.codeInsight.intentions.PyRelativeToAbsoluteImportIntention</className>
|
||||
<category>Python</category>
|
||||
</intentionAction>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.jetbrains.python.codeInsight.intentions.ImportToggleAliasIntention</className>
|
||||
<category>Python</category>
|
||||
|
||||
@@ -298,6 +298,10 @@ INTN.convert.static.method.to.function=Convert static method to function
|
||||
#PyConvertMethodToPropertyIntention
|
||||
INTN.convert.method.to.property=Convert method to property
|
||||
|
||||
#PyConvertImportIntentionAction
|
||||
INTN.convert.relative.to.absolute=Convert relative import to absolute
|
||||
INTN.convert.absolute.to.relative=Convert absolute import to relative
|
||||
|
||||
# Conflict checker
|
||||
CONFLICT.name.$0.obscured=Name ''{0}'' obscured by local definitions
|
||||
CONFLICT.name.$0.obscured.cannot.convert=Name ''{0}'' obscured. Cannot convert.
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// 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.codeInsight.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.jetbrains.python.codeInsight.imports.PyRelativeImportData
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameFinder
|
||||
|
||||
/**
|
||||
* Converts location of 'from one.two.three import foo' to the one relative to the current file, e.g. 'from .three import foo'.
|
||||
*
|
||||
* @see PyRelativeToAbsoluteImportIntention
|
||||
* @author Aleksei.Kniazev
|
||||
*/
|
||||
class PyAbsoluteToRelativeImportIntention : PyConvertImportIntentionAction("INTN.convert.absolute.to.relative") {
|
||||
|
||||
override fun doInvoke(project: Project, editor: Editor, file: PsiFile) {
|
||||
val statement = findStatement(file, editor) ?: return
|
||||
val targetPath = statement.importSourceQName ?: return
|
||||
val importData = PyRelativeImportData.fromString(targetPath.toString(), file as PyFile) ?: return
|
||||
|
||||
replaceImportStatement(statement, file, importData.locationWithDots)
|
||||
}
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
|
||||
if (file !is PyFile) return false
|
||||
val statement = findStatement(file, editor) ?: return false
|
||||
if (statement.relativeLevel != 0) return false
|
||||
|
||||
val targetPath = statement.importSourceQName ?: return false
|
||||
val filePath = QualifiedNameFinder.findCanonicalImportPath(file, null) ?: return false
|
||||
return targetPath.firstComponent == filePath.firstComponent
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// 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.codeInsight.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.psi.PyElementGenerator
|
||||
import com.jetbrains.python.psi.PyFromImportStatement
|
||||
import com.jetbrains.python.psi.PyStatement
|
||||
import org.jetbrains.annotations.PropertyKey
|
||||
|
||||
abstract class PyConvertImportIntentionAction(@PropertyKey(resourceBundle = "com.jetbrains.python.PyBundle") intentionText: String) : PyBaseIntentionAction() {
|
||||
|
||||
init {
|
||||
text = PyBundle.message(intentionText)
|
||||
}
|
||||
|
||||
override fun getFamilyName(): String = text
|
||||
|
||||
fun replaceImportStatement(statement: PyFromImportStatement, file: PsiFile, path: String) {
|
||||
val imported = statement.importElements.joinToString(", ") { it.text }
|
||||
|
||||
val generator = PyElementGenerator.getInstance(file.project)
|
||||
val languageLevel = LanguageLevel.forElement(file)
|
||||
val generatedStatement = generator.createFromImportStatement(languageLevel, path, imported, null)
|
||||
val formattedStatement = CodeStyleManager.getInstance(file.project).reformat(generatedStatement)
|
||||
statement.replace(formattedStatement)
|
||||
}
|
||||
|
||||
fun findStatement(file: PsiFile, editor: Editor): PyFromImportStatement? {
|
||||
val position = file.findElementAt(editor.caretModel.offset)
|
||||
return PsiTreeUtil.getParentOfType(position, PyFromImportStatement::class.java, true, PyStatement::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// 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.codeInsight.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.jetbrains.extensions.getQName
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
|
||||
/**
|
||||
* Converts location of 'from .three import foo' to absolute path, e.g. 'from one.two.three import foo'.
|
||||
*
|
||||
* @see PyRelativeToAbsoluteImportIntention
|
||||
* @author Aleksei.Kniazev
|
||||
*/
|
||||
class PyRelativeToAbsoluteImportIntention : PyConvertImportIntentionAction("INTN.convert.relative.to.absolute") {
|
||||
|
||||
override fun doInvoke(project: Project, editor: Editor, file: PsiFile) {
|
||||
val statement = findStatement(file, editor) ?: return
|
||||
val source = statement.resolveImportSource() ?: return
|
||||
val qName = source.getQName()
|
||||
|
||||
replaceImportStatement(statement, file, qName.toString())
|
||||
}
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
|
||||
if (file !is PyFile) return false
|
||||
val statement = findStatement(file, editor) ?: return false
|
||||
return statement.relativeLevel > 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
from .three import foo
|
||||
@@ -0,0 +1 @@
|
||||
from one.two.t<caret>hree import foo
|
||||
@@ -0,0 +1 @@
|
||||
foo = "bar"
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
def baz():
|
||||
pass
|
||||
+1
@@ -0,0 +1 @@
|
||||
from foo.ba<caret>r import baz
|
||||
+1
@@ -0,0 +1 @@
|
||||
from .lib import foo
|
||||
@@ -0,0 +1 @@
|
||||
from pkg.li<caret>b import foo
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def baz():
|
||||
pass
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from . import foo, \
|
||||
bar, \
|
||||
baz
|
||||
+1
@@ -0,0 +1 @@
|
||||
from one.t<caret>wo import foo, bar, baz
|
||||
@@ -0,0 +1 @@
|
||||
from collec<caret>tions import OrderedDict
|
||||
@@ -0,0 +1,8 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def baz():
|
||||
pass
|
||||
@@ -0,0 +1,5 @@
|
||||
from . import (
|
||||
foo,
|
||||
bar,
|
||||
baz,
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
from one.t<caret>wo import (
|
||||
foo,
|
||||
bar,
|
||||
baz,
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
from one.two.three import foo
|
||||
@@ -0,0 +1 @@
|
||||
from .t<caret>hree import foo
|
||||
@@ -0,0 +1 @@
|
||||
foo = "bar"
|
||||
+1
@@ -0,0 +1 @@
|
||||
from pkg.lib import foo
|
||||
@@ -0,0 +1 @@
|
||||
from .li<caret>b import foo
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
pass
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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.intentions
|
||||
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonTestUtil
|
||||
|
||||
class PyConvertImportIntentionTest : PyIntentionTestCase() {
|
||||
override fun getTestDataPath(): String = PythonTestUtil.getTestDataPath() + "/intentions/convertImport"
|
||||
|
||||
// PY-37858
|
||||
fun testAbsoluteToRelative() = doTestWithMultipleFiles(PyBundle.message("INTN.convert.absolute.to.relative"))
|
||||
|
||||
// PY-37858
|
||||
fun testRelativeToAbsolute() = doTestWithMultipleFiles(PyBundle.message("INTN.convert.relative.to.absolute"))
|
||||
|
||||
// PY-37858
|
||||
fun testAbsoluteToRelativeInInitFile() = doTestWithMultipleFiles(PyBundle.message("INTN.convert.absolute.to.relative"), "pkg/__init__")
|
||||
|
||||
// PY-37858
|
||||
fun testRelativeToAbsoluteInInitFile() = doTestWithMultipleFiles(PyBundle.message("INTN.convert.relative.to.absolute"), "pkg/__init__")
|
||||
|
||||
// PY-37858
|
||||
fun testPreservingCodestyle() {
|
||||
pythonCodeStyleSettings.FROM_IMPORT_WRAPPING = CommonCodeStyleSettings.WRAP_ALWAYS
|
||||
pythonCodeStyleSettings.FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS = true
|
||||
pythonCodeStyleSettings.FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = true
|
||||
pythonCodeStyleSettings.FROM_IMPORT_PARENTHESES_FORCE_IF_MULTILINE = true
|
||||
pythonCodeStyleSettings.FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true
|
||||
|
||||
doTestWithMultipleFiles(PyBundle.message("INTN.convert.absolute.to.relative"))
|
||||
}
|
||||
|
||||
// PY-37858
|
||||
fun testForcingCodestyleIfCurrentIsDifferent() {
|
||||
pythonCodeStyleSettings.FROM_IMPORT_WRAPPING = CommonCodeStyleSettings.WRAP_ALWAYS
|
||||
|
||||
doTestWithMultipleFiles(PyBundle.message("INTN.convert.absolute.to.relative"))
|
||||
}
|
||||
|
||||
// PY-37858
|
||||
fun testAbsoluteToRelativeForNonOverlappingLocations() {
|
||||
val root = getTestName(true)
|
||||
myFixture.copyDirectoryToProject(root, "")
|
||||
val file = myFixture.configureByFile("pkg/test.py")
|
||||
|
||||
val intentionActions = myFixture.filterAvailableIntentions(PyBundle.message("INTN.convert.absolute.to.relative"))
|
||||
assertEmpty(intentionActions)
|
||||
assertSdkRootsNotParsed(file)
|
||||
}
|
||||
|
||||
// PY-37858
|
||||
fun testNoConvertingStdLibToRelative() = doNegativeTest(PyBundle.message("INTN.convert.absolute.to.relative"))
|
||||
|
||||
|
||||
private fun doTestWithMultipleFiles(hint: String, file: String = "one/two/test") {
|
||||
val root = getTestName(true)
|
||||
myFixture.copyDirectoryToProject(root, "")
|
||||
myFixture.configureByFile("$file.py")
|
||||
|
||||
val intentionAction = myFixture.findSingleIntention(hint)
|
||||
assertNotNull(intentionAction)
|
||||
myFixture.launchAction(intentionAction)
|
||||
myFixture.checkResultByFile("$root/$file.after.py", true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user