mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-78749 Packages: refactor install utils, migrate methods to PPMUI
Signed-off-by: Nikita.Ashihmin <nikita.ashihmin@jetbrains.com> GitOrigin-RevId: 69eaeb90dd326f7e5cb07d67f615e939980d536b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ec7fcf0a8e
commit
f5d815d1e4
@@ -114,12 +114,15 @@ sealed class Result<out SUCC, out ERR> {
|
||||
fun <S> success(value: S): Success<S> = Success(value)
|
||||
fun <E> failure(error: E): Failure<E> = Failure(error)
|
||||
fun localizedError(message: @Nls String): Failure<MessageError> = failure(MessageError(message))
|
||||
|
||||
@ApiStatus.Internal
|
||||
inline fun <T> runCatching(body: () -> T): PyResult<T> = pyRunCatching(body)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UsagesOfObsoleteApi")
|
||||
@ApiStatus.Internal
|
||||
fun <T> pyRunCatching(body: () -> T): PyResult<T> {
|
||||
inline fun <T> pyRunCatching(body: () -> T): PyResult<T> {
|
||||
return try {
|
||||
PyResult.success(body())
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@
|
||||
testServiceImplementation="com.jetbrains.python.codeInsight.completion.DummyRemoteFilePathRetrievalService"/>
|
||||
|
||||
<applicationService serviceImplementation="com.jetbrains.python.sdk.PySdkSettings"/>
|
||||
<applicationService serviceImplementation="com.jetbrains.python.packaging.pip.PypiPackageCache"/>
|
||||
<applicationService serviceImplementation="com.jetbrains.python.packaging.PyPackageService"/>
|
||||
<applicationService serviceInterface="com.jetbrains.python.module.PyModuleService"
|
||||
serviceImplementation="com.jetbrains.python.module.PyModuleServiceImpl"/>
|
||||
|
||||
@@ -4,34 +4,25 @@ package com.jetbrains.python.inspections.quickfix
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyPsiPackageUtil.moduleToPackageName
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils
|
||||
import com.jetbrains.python.packaging.pyRequirement
|
||||
import com.jetbrains.python.packaging.management.ui.PythonPackageManagerUI
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import com.jetbrains.python.statistics.PyPackagesUsageCollector
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
class InstallAllPackagesQuickFix(private val packageNames: List<String>) : LocalQuickFix {
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val element = descriptor.psiElement ?: return
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
|
||||
val sdk = PythonSdkUtil.findPythonSdk(element) ?: return
|
||||
|
||||
val normalizedPackageNames = packageNames.map { moduleToPackageName(it) }
|
||||
val pyRequirements = normalizedPackageNames.map { pyRequirement(it) }
|
||||
val confirmedPackages = PyPackageInstallUtils.getConfirmedPackages(pyRequirements, project)
|
||||
|
||||
if (confirmedPackages.isEmpty()) return
|
||||
|
||||
val fix = PyInstallRequirementsFix(familyName, sdk,
|
||||
confirmedPackages.toList(),
|
||||
emptyList())
|
||||
fix.applyFix(module.project, descriptor)
|
||||
PyPackagesUsageCollector.installAllEvent.log(confirmedPackages.size)
|
||||
PyPackageCoroutine.launch(project) {
|
||||
PythonPackageManagerUI.forSdk(project, sdk).installWithConfirmation(normalizedPackageNames)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFamilyName(): @Nls String = PyBundle.message("python.unresolved.reference.inspection.install.all")
|
||||
|
||||
@@ -4,13 +4,10 @@ package com.jetbrains.python.inspections.quickfix
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.inspections.requirement.RunningPackagingTasksListener
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils.confirmInstall
|
||||
import com.jetbrains.python.packaging.pyRequirement
|
||||
import com.jetbrains.python.packaging.management.ui.PythonPackageManagerUI
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import com.jetbrains.python.statistics.PyPackagesUsageCollector
|
||||
import org.jetbrains.annotations.Nls
|
||||
@@ -19,22 +16,10 @@ internal open class InstallPackageQuickFix(open val packageName: String) : Local
|
||||
override fun getFamilyName(): @Nls String = PyBundle.message("python.unresolved.reference.inspection.install.package", packageName)
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
if (!confirmInstall(project, packageName)) return
|
||||
|
||||
descriptor.psiElement.let { element ->
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return
|
||||
val sdk = PythonSdkUtil.findPythonSdk(element) ?: return
|
||||
|
||||
PyInstallRequirementsFix(
|
||||
familyName, sdk,
|
||||
listOf(pyRequirement(packageName)),
|
||||
listener = object : RunningPackagingTasksListener(module) {
|
||||
override fun finished(exceptions: List<ExecutionException>) {
|
||||
onSuccess(descriptor)
|
||||
}
|
||||
}
|
||||
).applyFix(module.project, descriptor)
|
||||
|
||||
val sdk = PythonSdkUtil.findPythonSdk(descriptor.psiElement) ?: return
|
||||
PyPackageCoroutine.launch(project) {
|
||||
PythonPackageManagerUI.forSdk(project, sdk).installWithConfirmation(listOf(packageName)) ?: return@launch
|
||||
onSuccess(descriptor)
|
||||
PyPackagesUsageCollector.installSingleEvent.log()
|
||||
}
|
||||
}
|
||||
@@ -43,11 +28,7 @@ internal open class InstallPackageQuickFix(open val packageName: String) : Local
|
||||
|
||||
override fun availableInBatchMode(): Boolean = false
|
||||
|
||||
open fun onSuccess(descriptor: ProblemDescriptor?) { }
|
||||
open fun onSuccess(descriptor: ProblemDescriptor?) {}
|
||||
|
||||
override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo = IntentionPreviewInfo.EMPTY
|
||||
|
||||
companion object {
|
||||
const val CONFIRM_PACKAGE_INSTALLATION_PROPERTY: String = "python.confirm.package.installation"
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.inspections.quickfix
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.core.CoreBundle
|
||||
import com.intellij.model.SideEffectGuard
|
||||
import com.intellij.model.SideEffectGuard.Companion.checkSideEffectAllowed
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.inspections.PyInterpreterInspection
|
||||
import com.jetbrains.python.inspections.requirement.RunningPackagingTasksListener
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.management.ui.PythonPackageManagerUI
|
||||
import com.jetbrains.python.packaging.management.ui.installPyRequirementsBackground
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.adminPermissionsNeeded
|
||||
import com.jetbrains.python.ui.PyUiUtil
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
internal class PyInstallRequirementsFix(
|
||||
private val quickFixName: @Nls String?,
|
||||
private val sdk: Sdk,
|
||||
private val unsatisfied: List<PyRequirement>,
|
||||
private val installOptions: List<String> = emptyList(),
|
||||
private val listener: RunningPackagingTasksListener? = null,
|
||||
) : LocalQuickFix {
|
||||
|
||||
override fun getFamilyName(): @Nls String = quickFixName ?: PyPsiBundle.message( "QFIX.NAME.install.requirements", unsatisfied.size)
|
||||
|
||||
override fun startInWriteAction(): Boolean = false
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
if (hasAdminPermissionsAndConfigureInterpreter(project, descriptor, sdk)) return
|
||||
checkSideEffectAllowed(SideEffectGuard.EffectType.PROJECT_MODEL)
|
||||
PyUiUtil.clearFileLevelInspectionResults(descriptor.psiElement.containingFile)
|
||||
PyPackageCoroutine.launch(project) {
|
||||
listener?.started()
|
||||
val pythonPackageManagerUI = PythonPackageManagerUI.forSdk(project, sdk)
|
||||
pythonPackageManagerUI.installPyRequirementsBackground(unsatisfied, installOptions)
|
||||
listener?.finished(emptyList())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun hasAdminPermissionsAndConfigureInterpreter(
|
||||
project: Project,
|
||||
descriptor: ProblemDescriptor,
|
||||
sdk: Sdk,
|
||||
): Boolean {
|
||||
if (PythonSdkUtil.isRemote(sdk) || !sdk.adminPermissionsNeeded()) return false
|
||||
|
||||
val answer = askToConfigureInterpreter(project, sdk)
|
||||
if (answer == Messages.YES) {
|
||||
PyInterpreterInspection.ConfigureInterpreterFix().applyFix(project, descriptor)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if:
|
||||
* - The user agreed to configure the interpreter (`Messages.YES`).
|
||||
* - The user canceled the dialog (`Messages.CANCEL`).
|
||||
* - The response is indeterminate or invalid (`-1`).
|
||||
*/
|
||||
return answer == Messages.YES || answer == Messages.CANCEL || answer == -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog asking the user to configure an interpreter, proceed with installation, or cancel the operation.
|
||||
*
|
||||
* @param project The project in which the dialog is displayed.
|
||||
* @param sdk The SDK for which the operation is being prompted.
|
||||
* @return An integer representing the user's choice:
|
||||
* <ul>
|
||||
* <li><code>0</code>: The user selected the "Configure" option.</li>
|
||||
* <li><code>1</code>: The user selected the "Install Anyway" option.</li>
|
||||
* <li><code>2</code>: The user selected the "Cancel" option.</li>
|
||||
* </ul>
|
||||
*/
|
||||
private fun askToConfigureInterpreter(project: Project, sdk: Sdk): Int {
|
||||
val sdkName = StringUtil.shortenTextWithEllipsis(sdk.name, SDK_NAME_MAX_LENGTH, 0)
|
||||
val text = PyPsiBundle.message("INSP.package.requirements.administrator.privileges.required.description", sdkName)
|
||||
val options = arrayOf(
|
||||
PyPsiBundle.message("INSP.package.requirements.administrator.privileges.required.button.configure"),
|
||||
PyPsiBundle.message("INSP.package.requirements.administrator.privileges.required.button.install.anyway"),
|
||||
CoreBundle.message("button.cancel")
|
||||
)
|
||||
return Messages.showIdeaMessageDialog(
|
||||
project,
|
||||
text,
|
||||
PyPsiBundle.message("INSP.package.requirements.administrator.privileges.required"),
|
||||
options,
|
||||
DEFAULT_OPTION_INDEX,
|
||||
Messages.getWarningIcon(),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_OPTION_INDEX = 0
|
||||
private const val SDK_NAME_MAX_LENGTH = 25
|
||||
}
|
||||
}
|
||||
+15
-8
@@ -21,6 +21,7 @@ import com.intellij.psi.util.QualifiedName
|
||||
import com.intellij.psi.util.parentOfType
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.PyPsiPackageUtil
|
||||
import com.jetbrains.python.codeInsight.PyCodeInsightSettings
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
|
||||
import com.jetbrains.python.codeInsight.imports.AutoImportHintAction
|
||||
@@ -30,8 +31,9 @@ import com.jetbrains.python.getEffectiveLanguageLevel
|
||||
import com.jetbrains.python.inspections.PyInspectionVisitor
|
||||
import com.jetbrains.python.inspections.PyUnresolvedReferenceQuickFixProvider
|
||||
import com.jetbrains.python.inspections.quickfix.*
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils
|
||||
import com.jetbrains.python.packaging.PyPackageUtil
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.isNotInstalledAndCanBeInstalled
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.impl.PyFromImportStatementImpl
|
||||
import com.jetbrains.python.psi.impl.PyImportElementImpl
|
||||
@@ -86,8 +88,8 @@ class PyUnresolvedReferencesInspection : PyUnresolvedReferencesInspectionBase()
|
||||
if (components.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val packageName = PyPsiPackageUtil.moduleToPackageName(components[0])
|
||||
|
||||
val packageName = components[0]
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(node)
|
||||
val sdk = PythonSdkUtil.findPythonSdk(module)
|
||||
if (module == null || sdk == null || !PyPackageUtil.packageManagementEnabled(sdk, false, true)) {
|
||||
@@ -95,9 +97,12 @@ class PyUnresolvedReferencesInspection : PyUnresolvedReferencesInspectionBase()
|
||||
}
|
||||
|
||||
|
||||
val pyPackage = PyPackageInstallUtils.offeredPackageForNotFoundModule(module.project, sdk, packageName)
|
||||
val packageCandidates = listOfNotNull(pyPackage)
|
||||
return packageCandidates.map { pkg: String -> InstallPackageQuickFix(pkg) }
|
||||
val packageManager = PythonPackageManager.forSdk(module.project, sdk)
|
||||
|
||||
val shouldBeSuggest = packageManager.isNotInstalledAndCanBeInstalled(packageName)
|
||||
if (!shouldBeSuggest)
|
||||
return emptyList()
|
||||
return listOfNotNull(InstallPackageQuickFix(packageName))
|
||||
}
|
||||
|
||||
override fun getInstallAllPackagesQuickFix(): InstallAllPackagesQuickFix {
|
||||
@@ -180,9 +185,11 @@ class PyUnresolvedReferencesInspection : PyUnresolvedReferencesInspectionBase()
|
||||
return inspectionProfile.getUnwrappedTool(SHORT_NAME_KEY.toString(), element) as PyUnresolvedReferencesInspection?
|
||||
}
|
||||
|
||||
private fun createInstallAndImportQuickFix(project: Project, pythonSdk: Sdk, packageName: String, asName: String?): LocalQuickFix? {
|
||||
return if (PyPackageInstallUtils.checkShouldToInstallSnapshot(project, pythonSdk, packageName))
|
||||
InstallAndImportPackageQuickFix(packageName, asName)
|
||||
private fun createInstallAndImportQuickFix(project: Project, pythonSdk: Sdk, importedModuleName: String, asName: String?): LocalQuickFix? {
|
||||
val packageName = PyPsiPackageUtil.moduleToPackageName(importedModuleName)
|
||||
val canBeInstalled = PythonPackageManager.forSdk(project, pythonSdk).isNotInstalledAndCanBeInstalled(packageName)
|
||||
return if (canBeInstalled)
|
||||
InstallAndImportPackageQuickFix(importedModuleName, asName)
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.DoNotAskOption
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder.Companion.yesNo
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PyPsiPackageUtil
|
||||
import com.jetbrains.python.codeInsight.stdlib.PyStdlibUtil
|
||||
import com.jetbrains.python.inspections.quickfix.InstallPackageQuickFix
|
||||
import com.jetbrains.python.packaging.PyPIPackageUtil.INSTANCE
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.hasInstalledPackageSnapshot
|
||||
import com.jetbrains.python.packaging.management.ui.PythonPackageManagerUI
|
||||
import com.jetbrains.python.packaging.management.ui.installPyRequirementsBackground
|
||||
import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec
|
||||
import com.jetbrains.python.packaging.ui.PyChooseRequirementsDialog
|
||||
import com.jetbrains.python.statistics.PyPackagesUsageCollector
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* PyCharm doesn't provide any API for package management for external plugins.
|
||||
* The closest thing is [PythonPackageManager], although it is also subject to change
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
object PyPackageInstallUtils {
|
||||
internal fun getConfirmedPackages(packageNames: List<PyRequirement>, project: Project): Set<PyRequirement> {
|
||||
val confirmationEnabled = PropertiesComponent.getInstance()
|
||||
.getBoolean(InstallPackageQuickFix.CONFIRM_PACKAGE_INSTALLATION_PROPERTY, true)
|
||||
|
||||
if (!confirmationEnabled || packageNames.isEmpty()) return packageNames.toSet()
|
||||
|
||||
val dialog = PyChooseRequirementsDialog(project, packageNames) { it.presentableTextWithoutVersion }
|
||||
|
||||
if (!dialog.showAndGet()) {
|
||||
PyPackagesUsageCollector.installAllCanceledEvent.log()
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
return dialog.markedElements.toSet()
|
||||
}
|
||||
|
||||
|
||||
fun offeredPackageForNotFoundModule(project: Project, sdk: Sdk, moduleName: String): String? {
|
||||
val shouldToInstall = checkShouldToInstallSnapshot(project, sdk, moduleName)
|
||||
if (!shouldToInstall)
|
||||
return null
|
||||
return PyPsiPackageUtil.moduleToPackageName(moduleName)
|
||||
}
|
||||
|
||||
fun checkShouldToInstallSnapshot(project: Project, sdk: Sdk, moduleName: String): Boolean {
|
||||
val packageName = PyPsiPackageUtil.moduleToPackageName(moduleName)
|
||||
return !checkIsInstalledSnapshot(project, sdk, packageName) && INSTANCE.isInPyPI(packageName)
|
||||
}
|
||||
|
||||
fun checkIsInstalledSnapshot(project: Project, sdk: Sdk, packageName: String): Boolean {
|
||||
val isStdLib = PyStdlibUtil.getPackages()?.contains(packageName) ?: false
|
||||
if (isStdLib) {
|
||||
return true
|
||||
}
|
||||
val packageManager = PythonPackageManager.forSdk(project, sdk)
|
||||
return packageManager.hasInstalledPackageSnapshot(packageName)
|
||||
}
|
||||
|
||||
suspend fun confirmAndInstall(project: Project, sdk: Sdk, requirement: PyRequirement) {
|
||||
val isConfirmed = withContext(Dispatchers.EDT) {
|
||||
confirmInstall(project, requirement.name)
|
||||
}
|
||||
if (!isConfirmed)
|
||||
return
|
||||
PythonPackageManagerUI.forSdk(project, sdk).installPyRequirementsBackground(listOf(requirement))
|
||||
}
|
||||
|
||||
|
||||
suspend fun confirmAndInstall(project: Project, sdk: Sdk, packageName: String, versionSpec: PyRequirementVersionSpec? = null) {
|
||||
confirmAndInstall(project, sdk, pyRequirement(packageName, versionSpec))
|
||||
}
|
||||
|
||||
fun confirmInstall(project: Project, packageName: String): Boolean {
|
||||
val isWellKnownPackage = ApplicationManager.getApplication()
|
||||
.getService(PyPIPackageRanking::class.java)
|
||||
.packageRank.containsKey(packageName)
|
||||
val confirmationEnabled = PropertiesComponent.getInstance().getBoolean(InstallPackageQuickFix.CONFIRM_PACKAGE_INSTALLATION_PROPERTY, true)
|
||||
if (!isWellKnownPackage && confirmationEnabled) {
|
||||
val confirmed: Boolean = yesNo(PyBundle.message("python.packaging.dialog.title.install.package.confirmation"),
|
||||
PyBundle.message("python.packaging.dialog.message.install.package.confirmation", packageName))
|
||||
.icon(AllIcons.General.WarningDialog)
|
||||
.doNotAsk(ConfirmPackageInstallationDoNotAskOption())
|
||||
.ask(project)
|
||||
if (!confirmed) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
private class ConfirmPackageInstallationDoNotAskOption : DoNotAskOption.Adapter() {
|
||||
override fun rememberChoice(isSelected: Boolean, exitCode: Int) {
|
||||
if (isSelected && exitCode == Messages.OK) {
|
||||
PropertiesComponent.getInstance().setValue(InstallPackageQuickFix.CONFIRM_PACKAGE_INSTALLATION_PROPERTY, false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,11 @@ fun PythonPackageManager.hasInstalledPackageSnapshot(packageName: String, versio
|
||||
getInstalledPackageSnapshot(packageName, version) != null
|
||||
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun PythonPackageManager.isNotInstalledAndCanBeInstalled(packageName: String, version: String? = null): Boolean =
|
||||
!hasInstalledPackageSnapshot(packageName, version) && repositoryManager.hasPackageSnapshot(packageName)
|
||||
|
||||
|
||||
@ApiStatus.Internal
|
||||
suspend fun PythonPackageManager.findPackageSpecification(
|
||||
packageName: String,
|
||||
|
||||
@@ -35,6 +35,11 @@ interface PythonRepositoryManager {
|
||||
return repository.getPackages().filter { StringUtil.containsIgnoreCase(normalizePackageName(it), normalizedQuery) }
|
||||
}
|
||||
|
||||
|
||||
fun hasPackageSnapshot(packageName: String): Boolean {
|
||||
return repositories.any { packageName in it.getPackages() }
|
||||
}
|
||||
|
||||
suspend fun findPackageSpecification(
|
||||
requirement: PyRequirement,
|
||||
repository: PyPackageRepository? = null,
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging.management.ui
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DoNotAskOption
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.ui.PyChooseRequirementsDialog
|
||||
import com.jetbrains.python.statistics.PyPackagesUsageCollector
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
internal object PyPackageManagerUiConfirmationHelpers {
|
||||
private const val CONFIRM_PACKAGE_INSTALLATION_PROPERTY: String = "python.confirm.package.installation"
|
||||
|
||||
internal suspend fun getConfirmedPackages(pyRequirements: List<PyRequirement>, project: Project): List<PyRequirement> {
|
||||
if (pyRequirements.isEmpty())
|
||||
return emptyList()
|
||||
val confirmationEnabled = PropertiesComponent.getInstance()
|
||||
.getBoolean(CONFIRM_PACKAGE_INSTALLATION_PROPERTY, true)
|
||||
|
||||
if (!confirmationEnabled)
|
||||
return pyRequirements
|
||||
|
||||
if (pyRequirements.size == 1) {
|
||||
val pyPackage = pyRequirements.first()
|
||||
if (askSingleFileConfirmation(pyPackage, project)) {
|
||||
return listOf(pyPackage)
|
||||
}
|
||||
else {
|
||||
PyPackagesUsageCollector.installAllCanceledEvent.log()
|
||||
return listOf()
|
||||
}
|
||||
}
|
||||
|
||||
return withContext(Dispatchers.EDT) {
|
||||
val dialog = PyChooseRequirementsDialog(project, pyRequirements) { it.presentableText }
|
||||
val result = dialog.showAndGet()
|
||||
if (!result) {
|
||||
PyPackagesUsageCollector.installAllCanceledEvent.log()
|
||||
listOf()
|
||||
}
|
||||
else {
|
||||
dialog.markedElements
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun askSingleFileConfirmation(pyRequirement: PyRequirement, project: Project): Boolean = withContext(Dispatchers.EDT) {
|
||||
MessageDialogBuilder.yesNo(
|
||||
PyBundle.message("python.packaging.dialog.title.install.package.confirmation"),
|
||||
PyBundle.message("python.packaging.dialog.message.install.package.confirmation", pyRequirement.presentableText))
|
||||
.icon(AllIcons.General.WarningDialog)
|
||||
.doNotAsk(ConfirmPackageInstallationDoNotAskOption())
|
||||
.ask(project)
|
||||
}
|
||||
|
||||
|
||||
private class ConfirmPackageInstallationDoNotAskOption : DoNotAskOption.Adapter() {
|
||||
override fun rememberChoice(isSelected: Boolean, exitCode: Int) {
|
||||
if (isSelected && exitCode == Messages.OK) {
|
||||
PropertiesComponent.getInstance().setValue(CONFIRM_PACKAGE_INSTALLATION_PROPERTY, false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,13 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.PythonRepositoryPackageSpecification
|
||||
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.pyRequirement
|
||||
import com.jetbrains.python.statistics.PyPackagesUsageCollector
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
@@ -43,6 +46,22 @@ class PythonPackageManagerUI(val manager: PythonPackageManager, val sink: ErrorS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun installWithConfirmation(packages: List<String>): List<PythonPackage>? {
|
||||
val requirements = packages.map { pyRequirement(it) }
|
||||
return installPyRequirementsWithConfirmation(requirements)
|
||||
}
|
||||
|
||||
|
||||
suspend fun installPyRequirementsWithConfirmation(packages: List<PyRequirement>): List<PythonPackage>? {
|
||||
val confirmed = PyPackageManagerUiConfirmationHelpers.getConfirmedPackages(packages, project)
|
||||
if (confirmed.isEmpty())
|
||||
return null
|
||||
|
||||
PyPackagesUsageCollector.installAllEvent.log(confirmed.size)
|
||||
return installPyRequirementsBackground(confirmed)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of all installed packages or null if the operation was failed.
|
||||
*/
|
||||
@@ -102,20 +121,13 @@ class PythonPackageManagerUI(val manager: PythonPackageManager, val sink: ErrorS
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun syncBackground() {
|
||||
val progressTitle = PyBundle.message("python.packaging.sync.packages")
|
||||
executeCommand(progressTitle) {
|
||||
manager.sync()
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
suspend fun <T> executeCommand(
|
||||
progressTitle: @Nls String,
|
||||
operation: suspend (() -> PyResult<T>?),
|
||||
): T? = PythonPackageManagerUIHelpers.runPackagingOperationMaybeBackground(manager.project, sink, progressTitle) {
|
||||
|
||||
operation()
|
||||
operation()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+1
-2
@@ -3,7 +3,6 @@ package com.jetbrains.python.packaging.management.ui
|
||||
|
||||
import com.intellij.ui.awt.RelativePoint
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils.confirmAndInstall
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
@@ -42,7 +41,7 @@ fun PythonPackageManagerUI.launchInstallPackageWithBalloonBackground(packageName
|
||||
val loadBalloon = PythonPackageManagerUIHelpers.showBalloon(point, PyBundle.message("python.packaging.installing.package", packageName),
|
||||
PythonPackageManagerUIHelpers.BalloonStyle.INFO)
|
||||
try {
|
||||
confirmAndInstall(project, sdk, packageName, versionSpec = versionSpec)
|
||||
installPyRequirementsWithConfirmation(listOf(pyRequirement(packageName, versionSpec)))
|
||||
loadBalloon.hide()
|
||||
PyPackagesUsageCollector.installPackageFromConsole.log(project)
|
||||
PythonPackageManagerUIHelpers.showBalloon(point, PyBundle.message("python.packaging.notification.description.installed.packages", packageName), PythonPackageManagerUIHelpers.BalloonStyle.SUCCESS)
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.io.IdeUtilIoBundle
|
||||
import com.intellij.util.net.HttpConfigurable
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.PythonHelper
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -27,6 +26,7 @@ import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManagerEngine
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManagerRunner
|
||||
import com.jetbrains.python.packaging.utils.PyProxyUtils
|
||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||
import com.jetbrains.python.run.buildTargetedCommandLine
|
||||
import com.jetbrains.python.run.ensureProjectSdkAndModuleDirsAreOnTarget
|
||||
@@ -120,7 +120,7 @@ class PipPackageManagerEngine(
|
||||
|
||||
pythonExecution.addParameter(operation)
|
||||
if (operation == "install") {
|
||||
proxyString?.let {
|
||||
PyProxyUtils.proxyString?.let {
|
||||
pythonExecution.addParameter("--proxy")
|
||||
pythonExecution.addParameter(it)
|
||||
}
|
||||
@@ -182,15 +182,6 @@ class PipPackageManagerEngine(
|
||||
return@withContext PyResult.success(result.stdout)
|
||||
}
|
||||
|
||||
private val proxyString: String?
|
||||
get() {
|
||||
val settings = HttpConfigurable.getInstance()
|
||||
if (settings != null && settings.USE_HTTP_PROXY) {
|
||||
val credentials = if (settings.PROXY_AUTHENTICATION) "${settings.proxyLogin}:${settings.plainProxyPassword}@" else ""
|
||||
return "http://$credentials${settings.PROXY_HOST}:${settings.PROXY_PORT}"
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun partitionPackagesBySource(installRequest: PythonPackageInstallRequest): List<List<String>> {
|
||||
when (installRequest) {
|
||||
|
||||
@@ -37,8 +37,7 @@ private val LOG = logger<PypiPackageCache>()
|
||||
private val ALPHABET_REGEX = Regex("[-a-z0-9]+")
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Service
|
||||
class PypiPackageCache : PythonPackageCache<String> {
|
||||
open class PypiPackageCache : PythonPackageCache<String> {
|
||||
override val packages: Set<String>
|
||||
get() = cache
|
||||
|
||||
@@ -63,7 +62,7 @@ class PypiPackageCache : PythonPackageCache<String> {
|
||||
val filePath: Path = Paths.get(PathManager.getSystemPath(), "python_packages", "packages_v2.json")
|
||||
|
||||
@CheckReturnValue
|
||||
suspend fun reloadCache(force: Boolean = false): Result<Unit, IOException> {
|
||||
open suspend fun reloadCache(force: Boolean = false): Result<Unit, IOException> {
|
||||
lock.withLock {
|
||||
if ((cache.isNotEmpty() && !force) || loadInProgress) {
|
||||
return Result.success(Unit)
|
||||
@@ -93,7 +92,7 @@ class PypiPackageCache : PythonPackageCache<String> {
|
||||
return@withContext false
|
||||
}
|
||||
|
||||
var packageList = emptySet<String>()
|
||||
var packageList: Set<String>
|
||||
try {
|
||||
val type = object : TypeToken<LinkedHashSet<String>>() {}.type
|
||||
packageList = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging.utils
|
||||
|
||||
import com.intellij.util.net.HttpConfigurable
|
||||
|
||||
internal object PyProxyUtils {
|
||||
val proxyString: String?
|
||||
get() {
|
||||
val settings = HttpConfigurable.getInstance()
|
||||
if (settings != null && settings.USE_HTTP_PROXY) {
|
||||
val credentials = if (settings.PROXY_AUTHENTICATION) "${settings.proxyLogin}:${settings.plainProxyPassword}@" else ""
|
||||
return "http://$credentials${settings.PROXY_HOST}:${settings.PROXY_PORT}"
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+3
-13
@@ -6,12 +6,10 @@ import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.management.ui.PythonPackageManagerUI
|
||||
import com.jetbrains.python.packaging.management.ui.installPyRequirementsBackground
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.requirements.getPythonSdk
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
|
||||
internal class InstallAllRequirementsQuickFix(val requirements: List<PyRequirement>) : LocalQuickFix {
|
||||
override fun getFamilyName(): String {
|
||||
@@ -19,17 +17,9 @@ internal class InstallAllRequirementsQuickFix(val requirements: List<PyRequireme
|
||||
}
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
val confirmedPackages = PyPackageInstallUtils.getConfirmedPackages(requirements, project)
|
||||
if (confirmedPackages.isEmpty())
|
||||
return
|
||||
|
||||
val file = descriptor.psiElement.containingFile ?: return
|
||||
|
||||
val sdk = PythonSdkUtil.findPythonSdk(descriptor.psiElement) ?: return
|
||||
PyPackageCoroutine.launch(project) {
|
||||
val sdk = getPythonSdk(file) ?: return@launch
|
||||
val manager = PythonPackageManagerUI.forSdk(project, sdk)
|
||||
manager.installPyRequirementsBackground(confirmedPackages.toList(), emptyList<String>()
|
||||
)
|
||||
PythonPackageManagerUI.forSdk(project, sdk).installPyRequirementsWithConfirmation(requirements)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@ import com.intellij.codeInspection.LocalQuickFix
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils
|
||||
import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.management.ui.PythonPackageManagerUI
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.requirements.getPythonSdk
|
||||
|
||||
@@ -20,7 +20,7 @@ internal class InstallRequirementQuickFix(val requirement: PyRequirement) : Loca
|
||||
val pythonSdk = getPythonSdk(descriptor.psiElement.containingFile) ?: return
|
||||
|
||||
PyPackageCoroutine.launch(project) {
|
||||
PyPackageInstallUtils.confirmAndInstall(project, pythonSdk, requirement)
|
||||
PythonPackageManagerUI.forSdk(project, pythonSdk).installPyRequirementsWithConfirmation(listOf(requirement))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.modules
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.jetbrains.python.packaging.PyPackageInstallUtils
|
||||
import com.jetbrains.python.PyPsiPackageUtil
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManager
|
||||
import com.jetbrains.python.packaging.management.isNotInstalledAndCanBeInstalled
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.pythonSdk
|
||||
@@ -21,8 +23,15 @@ class PythonInstallPackageFilter(val project: Project, var editor: EditorImpl? =
|
||||
|
||||
val moduleName = line.removePrefix(prefix).dropLastWhile { it != '\'' }.dropLast(1)
|
||||
val pythonSdk = getSdkForFile(editor) ?: project.pythonSdk ?: project.modules.firstNotNullOfOrNull { it.pythonSdk } ?: return null
|
||||
val pipPackageName = PyPackageInstallUtils.offeredPackageForNotFoundModule(project, pythonSdk, moduleName) ?: return null
|
||||
val info = InstallPackageButtonItem(project, pythonSdk, entireLength - line.length + "ModuleNotFoundError:".length, pipPackageName)
|
||||
|
||||
val packageManager = PythonPackageManager.forSdk(project, pythonSdk)
|
||||
|
||||
val packageName = PyPsiPackageUtil.moduleToPackageName(moduleName)
|
||||
val isCanBeInstalled = packageManager.isNotInstalledAndCanBeInstalled(packageName)
|
||||
if (!isCanBeInstalled)
|
||||
return null
|
||||
|
||||
val info = InstallPackageButtonItem(project, pythonSdk, entireLength - line.length + "ModuleNotFoundError:".length, packageName)
|
||||
return Filter.Result(
|
||||
listOf(
|
||||
info,
|
||||
|
||||
@@ -1,867 +0,0 @@
|
||||
// Copyright 2000-2017 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.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.ex.InspectionProfileImpl;
|
||||
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.TestDataFile;
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
|
||||
import com.jetbrains.python.documentation.docstrings.DocStringFormat;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.inspections.*;
|
||||
import com.jetbrains.python.inspections.unresolvedReference.PyUnresolvedReferencesInspection;
|
||||
import com.jetbrains.python.packaging.PyPIPackageCache;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.quickFixes.PyRenameElementQuickFixTest;
|
||||
import org.intellij.lang.regexp.inspection.RegExpRedundantEscapeInspection;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@TestDataPath("$CONTENT_ROOT/../testData/inspections/")
|
||||
public class PyQuickFixTest extends PyTestCase {
|
||||
|
||||
@Override
|
||||
protected @Nullable LightProjectDescriptor getProjectDescriptor() {
|
||||
return ourPy2Descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
InspectionProfileImpl.INIT_INSPECTIONS = true;
|
||||
myFixture.setCaresAboutInjection(false);
|
||||
PyRenameElementQuickFixTest.registerTestNameSuggestionProvider(getTestRootDisposable());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
InspectionProfileImpl.INIT_INSPECTIONS = false;
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testAddImport() {
|
||||
doInspectionTest(new String[]{"AddImport.py", "ImportTarget.py"}, PyUnresolvedReferencesInspection.class,
|
||||
"Import 'ImportTarget'", true, true);
|
||||
}
|
||||
|
||||
public void testAddImportDoc() {
|
||||
doInspectionTest(new String[]{"AddImportDoc.py", "ImportTarget.py"}, PyUnresolvedReferencesInspection.class,
|
||||
"Import 'ImportTarget'", true, true);
|
||||
}
|
||||
|
||||
// PY-728
|
||||
public void testAddImportDocComment() {
|
||||
doInspectionTest(new String[]{"AddImportDocComment.py", "ImportTarget.py"}, PyUnresolvedReferencesInspection.class,
|
||||
"Import 'ImportTarget'", true, true);
|
||||
}
|
||||
|
||||
// PY-42307
|
||||
public void testInstallAndImportPackageByNameAlias() {
|
||||
//noinspection removal
|
||||
PyPIPackageCache.reload(List.of("pandas", "pd"));
|
||||
myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, "pd<caret>.array()");
|
||||
myFixture.findSingleIntention("Import 'turtle.pd'"); // standard library
|
||||
myFixture.findSingleIntention("Install and import package 'pd'"); // PyPI
|
||||
myFixture.findSingleIntention("Install and import package 'pandas'"); // 'pd' is a common import alias for 'pandas' from PyPI
|
||||
}
|
||||
|
||||
public void testImportFromModule() {
|
||||
doInspectionTest(new String[]{"importFromModule/foo/bar.py", "importFromModule/foo/baz.py", "importFromModule/foo/__init__.py"},
|
||||
PyUnresolvedReferencesInspection.class, "Import 'importFromModule.foo.baz'", true, true);
|
||||
}
|
||||
|
||||
// PY-14365
|
||||
public void testObjectBaseIsNotShownInAutoImportQuickfix() {
|
||||
myFixture.copyDirectoryToProject("objectBaseIsNotShownInAutoImportQuickfix", "");
|
||||
myFixture.configureByFile("main.py");
|
||||
myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
|
||||
final IntentionAction intention = myFixture.findSingleIntention("Import");
|
||||
assertNotNull(intention);
|
||||
assertEquals("Import 'module.MyOldStyleClass'", intention.getText());
|
||||
}
|
||||
|
||||
// PY-6302
|
||||
public void testImportFromModuleStar() {
|
||||
myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
|
||||
myFixture.copyDirectoryToProject("importFromModuleStar", "");
|
||||
myFixture.configureFromTempProjectFile("source.py");
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
final IntentionAction intentionAction = myFixture.findSingleIntention("Import 'target.xyzzy'");
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile("importFromModuleStar/source_after.py");
|
||||
}
|
||||
|
||||
public void testQualifyByImport() {
|
||||
final PyCodeInsightSettings settings = PyCodeInsightSettings.getInstance();
|
||||
boolean oldPreferFrom = settings.PREFER_FROM_IMPORT;
|
||||
settings.PREFER_FROM_IMPORT = false;
|
||||
try {
|
||||
doInspectionTest(new String[]{"QualifyByImport.py", "QualifyByImportFoo.py"}, PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("ACT.qualify.with.module"), true, true);
|
||||
}
|
||||
finally {
|
||||
settings.PREFER_FROM_IMPORT = oldPreferFrom;
|
||||
}
|
||||
}
|
||||
|
||||
public void testAddToImportFromList() {
|
||||
doInspectionTest(new String[]{"AddToImportFromList.py", "AddToImportFromFoo.py"}, PyUnresolvedReferencesInspection.class,
|
||||
"Import 'add_to_import_test_unique_name from AddToImportFromFoo'", true, true);
|
||||
}
|
||||
// TODO: add a test for multiple variants of above
|
||||
|
||||
// TODO: add tests for stub indexes-based autoimport of unimported somehow.
|
||||
|
||||
public void testAddSelf() {
|
||||
doInspectionTest(PyMethodParametersInspection.class, PyPsiBundle.message("QFIX.add.parameter.self", "self"), true, true);
|
||||
}
|
||||
|
||||
public void testReplacePrint() {
|
||||
doInspectionTest(PyCompatibilityInspection.class, PyPsiBundle.message("QFIX.statement.effect"), true, true);
|
||||
}
|
||||
|
||||
// PY-22045
|
||||
public void testBatchReplacePrintInsertsFutureImportOnlyOnce() {
|
||||
doInspectionTest(
|
||||
PyCompatibilityInspection.class,
|
||||
"Fix all 'Code is incompatible with specific Python versions' problems in file",
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// PY-4556
|
||||
public void testAddSelfFunction() {
|
||||
doInspectionTest("AddSelfFunction.py", PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.unresolved.reference", "get_a", "self"), true, true);
|
||||
}
|
||||
|
||||
// PY-9721
|
||||
public void testAddSelfToClassmethod() {
|
||||
doInspectionTest("AddSelfToClassmethod.py", PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.unresolved.reference", "foo", "cls"), true, true);
|
||||
}
|
||||
|
||||
public void testAddCls() {
|
||||
doInspectionTest(PyMethodParametersInspection.class, PyPsiBundle.message("QFIX.add.parameter.self", "cls"), true, true);
|
||||
}
|
||||
|
||||
public void testRenameToSelf() {
|
||||
doInspectionTest(PyMethodParametersInspection.class, PyPsiBundle.message("QFIX.rename.parameter", "self"), true, true);
|
||||
}
|
||||
|
||||
public void testRemoveTrailingSemicolon() {
|
||||
doInspectionTest(PyTrailingSemicolonInspection.class, PyPsiBundle.message("QFIX.remove.trailing.semicolon"), true, true);
|
||||
}
|
||||
|
||||
public void testDictCreation() {
|
||||
doInspectionTest(PyDictCreationInspection.class, PyPsiBundle.message("QFIX.dict.creation"), true, true);
|
||||
}
|
||||
|
||||
// PY-6283
|
||||
public void testDictCreationTuple() {
|
||||
doInspectionTest(PyDictCreationInspection.class, PyPsiBundle.message("QFIX.dict.creation"), true, true);
|
||||
}
|
||||
|
||||
// PY-7318
|
||||
public void testDictCreationDuplicate() {
|
||||
doInspectionTest(PyDictCreationInspection.class, PyPsiBundle.message("QFIX.dict.creation"), true, true);
|
||||
}
|
||||
|
||||
// PY-40177
|
||||
public void testDictCreationWithDoubleStars() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(),
|
||||
() -> doInspectionTest(PyDictCreationInspection.class, PyPsiBundle.message("QFIX.dict.creation"), true, true));
|
||||
}
|
||||
|
||||
public void testTransformClassicClass() {
|
||||
doInspectionTest(PyClassicStyleClassInspection.class, PyPsiBundle.message("QFIX.classic.class.transform"), true, true);
|
||||
}
|
||||
|
||||
public void testAddGlobalStatement() {
|
||||
doInspectionTest(PyUnboundLocalVariableInspection.class, PyPsiBundle.message("QFIX.add.global"), true, true);
|
||||
}
|
||||
|
||||
public void testAddGlobalExistingStatement() {
|
||||
doInspectionTest(PyUnboundLocalVariableInspection.class, PyPsiBundle.message("QFIX.add.global"), true, true);
|
||||
}
|
||||
|
||||
public void testSimplifyBooleanCheck() {
|
||||
doInspectionTest(PySimplifyBooleanCheckInspection.class, PyPsiBundle.message("QFIX.simplify.boolean.expression", "b"), true, true);
|
||||
}
|
||||
|
||||
public void testMoveFromFutureImport() {
|
||||
doInspectionTest(PyFromFutureImportInspection.class, PyPsiBundle.message("QFIX.move.from.future.import"), true, true);
|
||||
}
|
||||
|
||||
// PY-10080
|
||||
public void testMoveFromFutureImportDocString() {
|
||||
doInspectionTest(PyFromFutureImportInspection.class, PyPsiBundle.message("QFIX.move.from.future.import"), true, true);
|
||||
}
|
||||
|
||||
// PY-23475
|
||||
public void testMoveFromFutureImportAboveModuleLevelDunder() {
|
||||
doInspectionTest(PyFromFutureImportInspection.class, PyPsiBundle.message("QFIX.move.from.future.import"), true, true);
|
||||
}
|
||||
|
||||
public void testComparisonWithNone() {
|
||||
doInspectionTest(PyComparisonWithNoneInspection.class, PyPsiBundle.message("QFIX.replace.equality"), true, true);
|
||||
}
|
||||
|
||||
public void testAddClassFix() {
|
||||
doInspectionTest("AddClass.py", PyUnresolvedReferencesInspection.class, "Create class 'Xyzzy'", true, true);
|
||||
}
|
||||
|
||||
// PY-42389
|
||||
public void testAddClassFixPython3() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () ->
|
||||
doInspectionTest(PyUnresolvedReferencesInspection.class, "Create class 'Xyzzy'", true, true));
|
||||
}
|
||||
|
||||
// PY-21204
|
||||
public void testAddClassFromTypeComment() {
|
||||
doInspectionTest(PyUnresolvedReferencesInspection.class, "Create class 'MyClass'", true, true);
|
||||
}
|
||||
|
||||
// PY-21204
|
||||
public void testAddClassFromFString() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36,
|
||||
() -> doInspectionTest(PyUnresolvedReferencesInspection.class, "Create class 'MyClass'", true, true));
|
||||
}
|
||||
|
||||
// PY-33802
|
||||
public void testAddClassToImportedModule() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.class.in.module", "Clzz", "mod.py"), "mod.py");
|
||||
}
|
||||
|
||||
// PY-33802
|
||||
public void testAddClassToImportedPackage() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.class.in.module", "Clzz", "__init__.py"), "pkg/__init__.py");
|
||||
}
|
||||
|
||||
// PY-33802
|
||||
public void testAddClassToModuleInFromImport() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.class.in.module", "Clzz", "mod.py"), "mod.py");
|
||||
}
|
||||
|
||||
// PY-33802
|
||||
public void testAddClassToPackageInFromImport() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.class.in.module", "Clzz", "__init__.py"),
|
||||
"mypack/__init__.py");
|
||||
}
|
||||
|
||||
// PY-21204
|
||||
public void testAddFunctionFromFString() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36,
|
||||
() -> doInspectionTest(PyUnresolvedReferencesInspection.class, PyPsiBundle.message("QFIX.NAME.unresolved.reference.create.function", "my_function"), true, true));
|
||||
}
|
||||
|
||||
// PY-1465
|
||||
public void testAddFunctionToModuleInImport() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.function.in.module", "func", "mod.py"),
|
||||
"mod.py");
|
||||
}
|
||||
|
||||
// PY-34710
|
||||
public void testAddFunctionToModuleInFromImport() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.function.in.module", "foo", "mod.py"),
|
||||
"mod.py");
|
||||
}
|
||||
|
||||
// PY-34710
|
||||
public void testAddFunctionToPackageInFromImport() {
|
||||
doMultiFilesInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.create.function.in.module", "foo", "__init__.py"),
|
||||
"mypack/__init__.py");
|
||||
}
|
||||
|
||||
// PY-1470
|
||||
public void testRedundantParentheses() {
|
||||
String[] testFiles = {"RedundantParentheses.py"};
|
||||
myFixture.enableInspections(PyRedundantParenthesesInspection.class);
|
||||
myFixture.configureByFiles(testFiles);
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
final IntentionAction intentionAction = myFixture.findSingleIntention(PyPsiBundle.message("QFIX.redundant.parentheses"));
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after"));
|
||||
}
|
||||
|
||||
// PY-3095
|
||||
public void testRedundantParenthesesBoolean() {
|
||||
doInspectionTest(PyRedundantParenthesesInspection.class, PyPsiBundle.message("QFIX.redundant.parentheses"), true, true);
|
||||
}
|
||||
|
||||
// PY-3239
|
||||
public void testRedundantParenthesesMore() {
|
||||
doInspectionTest(PyRedundantParenthesesInspection.class, PyPsiBundle.message("QFIX.redundant.parentheses"), true, true);
|
||||
}
|
||||
|
||||
// PY-12679
|
||||
public void testRedundantParenthesesParenthesizedExpression() {
|
||||
doInspectionTest(PyRedundantParenthesesInspection.class, PyPsiBundle.message("QFIX.redundant.parentheses"), true, true);
|
||||
}
|
||||
|
||||
public void testRedundantParenthesesMultipleParentheses() {
|
||||
doInspectionTest(PyRedundantParenthesesInspection.class, PyPsiBundle.message("QFIX.redundant.parentheses"), true, true);
|
||||
}
|
||||
|
||||
// PY-15506
|
||||
public void testEmptyListOfBaseClasses() {
|
||||
doInspectionTest(PyRedundantParenthesesInspection.class, PyPsiBundle.message("QFIX.redundant.parentheses"), true, true);
|
||||
}
|
||||
|
||||
// PY-18203
|
||||
public void testRedundantParenthesesInTuples() {
|
||||
doInspectionTest(PyRedundantParenthesesInspection.class, PyPsiBundle.message("QFIX.redundant.parentheses"), true, true);
|
||||
}
|
||||
|
||||
// PY-1020
|
||||
public void testChainedComparisons() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-3126
|
||||
public void testChainedComparison1() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-3126
|
||||
public void testChainedComparison2() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-3126
|
||||
public void testChainedComparison3() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-5623
|
||||
public void testChainedComparison4() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-6467
|
||||
public void testChainedComparison5() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-20004
|
||||
public void testChainedComparison7() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-14002
|
||||
public void testChainedComparisonWithCommonBinaryExpression() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, PyPsiBundle.message("QFIX.chained.comparison"), true, true);
|
||||
}
|
||||
|
||||
// PY-19583
|
||||
public void testChainedComparison6() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, "Simplify chained comparison", true, true);
|
||||
}
|
||||
|
||||
// PY-24942
|
||||
public void testChainedComparison8() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, "Simplify chained comparison", true, true);
|
||||
}
|
||||
|
||||
// PY-29121
|
||||
public void testChainedComparison9() {
|
||||
doInspectionTest(PyChainedComparisonsInspection.class, "Simplify chained comparison", true, true);
|
||||
}
|
||||
|
||||
// PY-1362, PY-2585
|
||||
public void testStatementEffect() {
|
||||
doInspectionTest(PyStatementEffectInspection.class, PyPsiBundle.message("QFIX.statement.effect"), true, true);
|
||||
}
|
||||
|
||||
// PY-1265
|
||||
public void testStatementEffectIntroduceVariable() {
|
||||
doInspectionTest(PyStatementEffectInspection.class, PyPsiBundle.message("QFIX.introduce.variable"), true, true);
|
||||
}
|
||||
|
||||
// PY-2092
|
||||
public void testUnresolvedRefCreateFunction() {
|
||||
doInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.NAME.unresolved.reference.create.function", "ref"), true, true);
|
||||
}
|
||||
|
||||
public void testUnresolvedRefCreateAsyncFunction() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(),
|
||||
() -> doInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.NAME.unresolved.reference.create.function", "ref"), true, true));
|
||||
}
|
||||
|
||||
public void testUnresolvedRefNoCreateFunction() {
|
||||
myFixture.enableInspections(PyUnresolvedReferencesInspection.class);
|
||||
myFixture.configureByFile("UnresolvedRefNoCreateFunction.py");
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
final IntentionAction intentionAction = myFixture.getAvailableIntention(PyPsiBundle.message("QFIX.NAME.unresolved.reference.create.function", "ref"));
|
||||
assertNull(intentionAction);
|
||||
}
|
||||
|
||||
public void testReplaceNotEqOperator() {
|
||||
doInspectionTest(PyCompatibilityInspection.class, PyPsiBundle.message("INTN.replace.noteq.operator"), true, true);
|
||||
}
|
||||
|
||||
public void testListCreation() {
|
||||
doInspectionTest(PyListCreationInspection.class, PyPsiBundle.message("QFIX.list.creation"), true, true);
|
||||
}
|
||||
|
||||
// PY-16194
|
||||
public void testListCreationOnlyConsecutiveAppends() {
|
||||
doInspectionTest(PyListCreationInspection.class, PyPsiBundle.message("QFIX.list.creation"), true, true);
|
||||
}
|
||||
|
||||
// PY-1445
|
||||
public void testConvertSingleQuotedDocstring() {
|
||||
getIndentOptions().INDENT_SIZE = 2;
|
||||
doInspectionTest(PySingleQuotedDocstringInspection.class, PyPsiBundle.message("QFIX.convert.single.quoted.docstring"), true, true);
|
||||
}
|
||||
|
||||
// PY-8926
|
||||
public void testConvertSingleQuotedDocstringEscape() {
|
||||
getIndentOptions().INDENT_SIZE = 2;
|
||||
doInspectionTest(PySingleQuotedDocstringInspection.class, PyPsiBundle.message("QFIX.convert.single.quoted.docstring"), true, true);
|
||||
}
|
||||
|
||||
// PY-3127
|
||||
public void testDefaultArgument() {
|
||||
doInspectionTest(PyDefaultArgumentInspection.class, PyPsiBundle.message("QFIX.default.argument"), true, true);
|
||||
}
|
||||
|
||||
public void testDefaultArgumentEmptyList() {
|
||||
doInspectionTest(PyDefaultArgumentInspection.class, PyPsiBundle.message("QFIX.default.argument"), true, true);
|
||||
}
|
||||
|
||||
// PY-17392
|
||||
public void testDefaultArgumentCommentsInsideParameters() {
|
||||
doInspectionTest(PyDefaultArgumentInspection.class, PyPsiBundle.message("QFIX.default.argument"), true, true);
|
||||
}
|
||||
|
||||
// PY-3125
|
||||
public void testArgumentEqualDefault() {
|
||||
doInspectionTest(PyArgumentEqualDefaultInspection.class, PyPsiBundle.message("QFIX.remove.argument.equal.default"), true, true);
|
||||
}
|
||||
|
||||
// PY-3315
|
||||
public void testAddCallSuper() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-4017
|
||||
public void testAddCallSuper1() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-8654
|
||||
public void testAddCallSuperPass() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-15867
|
||||
public void testAddCallSuperOptionalAndRequiredParamsNameCollision() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-15927
|
||||
public void testAddCallSuperConflictingTupleParam() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-16036
|
||||
public void testAddCallSuperSelfNamePreserved() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-16420
|
||||
public void testAddCallSuperRepeatedOptionalParamsPassedToSuperConstructor() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-16420
|
||||
public void testAddCallSuperRepeatedOptionalTupleParamsPassedToSuperConstructor() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-16289
|
||||
public void testAddCallSuperCommentAfterColonPreserved() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-16348
|
||||
public void testAddCallSuperCommentsInFunctionBodyPreserved() {
|
||||
doInspectionTest(PyMissingConstructorInspection.class, PyPsiBundle.message("QFIX.add.super"), true, true);
|
||||
}
|
||||
|
||||
// PY-491, PY-13297
|
||||
public void testAddEncoding() {
|
||||
doInspectionTest(PyMandatoryEncodingInspection.class, PyPsiBundle.message("QFIX.add.encoding"), true, true);
|
||||
}
|
||||
|
||||
// PY-13297
|
||||
public void testAddEncodingAtLastLine() {
|
||||
doInspectionTest(PyMandatoryEncodingInspection.class, PyPsiBundle.message("QFIX.add.encoding"), true, true);
|
||||
}
|
||||
|
||||
// PY-3348
|
||||
public void testRemoveDecorator() {
|
||||
doInspectionTest(PyDecoratorInspection.class, PyPsiBundle.message("QFIX.remove.decorator"), true, true);
|
||||
}
|
||||
|
||||
public void testAddParameter() {
|
||||
doInspectionTest(PyUnresolvedReferencesInspection.class,
|
||||
PyPsiBundle.message("QFIX.unresolved.reference.add.param", "test"), true, true);
|
||||
}
|
||||
|
||||
// PY-6595
|
||||
public void testRenameUnresolvedReference() {
|
||||
doInspectionTest(PyUnresolvedReferencesInspection.class, PyPsiBundle.message("QFIX.rename.unresolved.reference"), true, true);
|
||||
}
|
||||
|
||||
// PY-3120
|
||||
public void testSetFunctionToLiteral() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON27, () -> doInspectionTest(PySetFunctionToLiteralInspection.class, PyPsiBundle.message("QFIX.replace.function.set.with.literal"), true, true));
|
||||
}
|
||||
|
||||
// PY-3394
|
||||
public void testDocstringParams() {
|
||||
getIndentOptions().INDENT_SIZE = 2;
|
||||
runWithDocStringFormat(DocStringFormat.REST,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.add.parameter", "b"), true, true));
|
||||
}
|
||||
|
||||
public void testDocstringParams1() {
|
||||
getIndentOptions().INDENT_SIZE = 2;
|
||||
runWithDocStringFormat(DocStringFormat.REST,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "c"), true, true));
|
||||
}
|
||||
|
||||
// PY-4964
|
||||
public void testDocstringParams2() {
|
||||
runWithDocStringFormat(DocStringFormat.REST,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.add.parameter", "ham"), true, true));
|
||||
}
|
||||
|
||||
// PY-9795
|
||||
public void testGoogleDocStringAddParam() {
|
||||
runWithDocStringFormat(DocStringFormat.GOOGLE,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.add.parameter", "b"), true, true));
|
||||
}
|
||||
|
||||
// PY-9795
|
||||
public void testGoogleDocStringRemoveParam() {
|
||||
runWithDocStringFormat(DocStringFormat.GOOGLE,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "c"), true, true));
|
||||
}
|
||||
|
||||
// PY-9795
|
||||
public void testGoogleDocStringRemoveParamWithSection() {
|
||||
runWithDocStringFormat(DocStringFormat.GOOGLE,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "c"), true, true));
|
||||
}
|
||||
|
||||
// PY-16761
|
||||
public void testGoogleDocStringRemovePositionalVararg() {
|
||||
runWithDocStringFormat(DocStringFormat.GOOGLE,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "args"), true, true));
|
||||
}
|
||||
|
||||
// PY-16761
|
||||
public void testGoogleDocStringRemoveKeywordVararg() {
|
||||
runWithDocStringFormat(DocStringFormat.GOOGLE, () -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "kwargs"), true, true));
|
||||
}
|
||||
|
||||
// PY-16908
|
||||
public void testNumpyDocStringRemoveFirstOfCombinedParams() {
|
||||
runWithDocStringFormat(DocStringFormat.NUMPY,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "x"), true, true));
|
||||
}
|
||||
|
||||
// PY-16908
|
||||
public void testNumpyDocStringRemoveMidOfCombinedParams() {
|
||||
runWithDocStringFormat(DocStringFormat.NUMPY,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "y"), true, true));
|
||||
}
|
||||
|
||||
// PY-16908
|
||||
public void testNumpyDocStringRemoveLastOfCombinedParams() {
|
||||
runWithDocStringFormat(DocStringFormat.NUMPY,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "z"), true, true));
|
||||
}
|
||||
|
||||
// PY-16908
|
||||
public void testNumpyDocStringRemoveCombinedVarargParam() {
|
||||
runWithDocStringFormat(DocStringFormat.NUMPY,
|
||||
() -> doInspectionTest(PyIncorrectDocstringInspection.class, PyPsiBundle.message("QFIX.docstring.remove.parameter", "args"), true, true));
|
||||
}
|
||||
|
||||
public void testUnnecessaryBackslash() {
|
||||
String[] testFiles = {"UnnecessaryBackslash.py"};
|
||||
myFixture.enableInspections(PyUnnecessaryBackslashInspection.class);
|
||||
myFixture.configureByFiles(testFiles);
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
IntentionAction intentionAction = myFixture.getAvailableIntention(PyPsiBundle.message("QFIX.remove.unnecessary.backslash"));
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after"));
|
||||
}
|
||||
|
||||
// PY-3051
|
||||
public void testUnresolvedRefTrueFalse() {
|
||||
doInspectionTest(PyUnresolvedReferencesInspection.class, PyPsiBundle.message("QFIX.replace.with.true.or.false", "True"), true, true);
|
||||
}
|
||||
|
||||
public void testUnnecessaryBackslashInArgumentList() {
|
||||
String[] testFiles = {"UnnecessaryBackslashInArguments.py"};
|
||||
myFixture.enableInspections(PyUnnecessaryBackslashInspection.class);
|
||||
myFixture.configureByFiles(testFiles);
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
IntentionAction intentionAction = myFixture.getAvailableIntention(PyPsiBundle.message("QFIX.remove.unnecessary.backslash"));
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after"));
|
||||
}
|
||||
|
||||
// PY-8788
|
||||
public void testRenameShadowingBuiltins() {
|
||||
final String fileName = "RenameShadowingBuiltins.py";
|
||||
myFixture.configureByFile(fileName);
|
||||
myFixture.enableInspections(PyShadowingBuiltinsInspection.class);
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
final IntentionAction intentionAction = myFixture.getAvailableIntention(PyBundle.message("QFIX.NAME.rename.element"));
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile(graftBeforeExt(fileName, "_after"));
|
||||
}
|
||||
|
||||
// PY-8788
|
||||
public void testRenameFunctionShadowingBuiltins() {
|
||||
final String fileName = "RenameFunctionShadowingBuiltins.py";
|
||||
myFixture.configureByFile(fileName);
|
||||
myFixture.enableInspections(PyShadowingBuiltinsInspection.class);
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
final IntentionAction intentionAction = myFixture.getAvailableIntention(PyBundle.message("QFIX.NAME.rename.element"));
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkResultByFile(graftBeforeExt(fileName, "_after"));
|
||||
}
|
||||
|
||||
public void testIgnoreShadowingBuiltins() {
|
||||
myFixture.configureByFile("IgnoreShadowingBuiltins.py");
|
||||
myFixture.enableInspections(PyShadowingBuiltinsInspection.class);
|
||||
final IntentionAction intentionAction = myFixture.getAvailableIntention("Ignore shadowed built-in name \"open\"");
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
myFixture.checkHighlighting(true, false, true);
|
||||
}
|
||||
|
||||
public void testImplementAbstractProperty() {
|
||||
doInspectionTest("ImplementAbstractProperty.py", PyAbstractClassInspection.class, PyBundle.message("QFIX.NAME.implement.methods"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
public void testImplementAbstractProperty1() {
|
||||
doInspectionTest("ImplementAbstractProperty.py", PyAbstractClassInspection.class, PyBundle.message("QFIX.NAME.implement.methods"),
|
||||
true, true);
|
||||
}
|
||||
|
||||
public void testImplementAbstractOrder() {
|
||||
doInspectionTest("ImplementAbstractOrder.py",
|
||||
PyAbstractClassInspection.class,
|
||||
PyBundle.message("QFIX.NAME.implement.methods"),
|
||||
true,
|
||||
true);
|
||||
}
|
||||
|
||||
public void testRemovingUnderscoresInNumericLiterals() {
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, "1_0_0");
|
||||
|
||||
final IntentionAction action = myFixture.findSingleIntention(PyPsiBundle.message("QFIX.NAME.remove.underscores.in.numeric"));
|
||||
myFixture.launchAction(action);
|
||||
|
||||
myFixture.checkResult("100");
|
||||
}
|
||||
|
||||
// PY-20452
|
||||
public void testRemoveRedundantEscapeInOnePartRegExp() {
|
||||
myFixture.enableInspections(new RegExpRedundantEscapeInspection());
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, "import re\nre.compile(\"(?P<foo>((<caret>\\/(?P<bar>.+))?))\")");
|
||||
|
||||
final List<IntentionAction> quickFixes = myFixture.getAllQuickFixes();
|
||||
assertEquals(1, quickFixes.size());
|
||||
|
||||
final IntentionAction removeRedundantEscapeFix = quickFixes.get(0);
|
||||
assertEquals("Remove redundant escape", removeRedundantEscapeFix.getText());
|
||||
|
||||
myFixture.launchAction(removeRedundantEscapeFix);
|
||||
myFixture.checkResult("import re\nre.compile(\"(?P<foo>((/(?P<bar>.+))?))\")");
|
||||
}
|
||||
|
||||
// PY-20452
|
||||
public void testRemoveRedundantEscapeInMultiPartRegExp() {
|
||||
myFixture.enableInspections(new RegExpRedundantEscapeInspection());
|
||||
myFixture.configureByText(PythonFileType.INSTANCE, """
|
||||
import re
|
||||
re.compile("(?P<foo>"
|
||||
"((<caret>\\/(?P<bar>.+))?))")""");
|
||||
|
||||
final List<IntentionAction> quickFixes = myFixture.getAllQuickFixes();
|
||||
assertEquals(1, quickFixes.size());
|
||||
|
||||
final IntentionAction removeRedundantEscapeFix = quickFixes.get(0);
|
||||
assertEquals("Remove redundant escape", removeRedundantEscapeFix.getText());
|
||||
|
||||
myFixture.launchAction(removeRedundantEscapeFix);
|
||||
myFixture.checkResult("""
|
||||
import re
|
||||
re.compile("(?P<foo>"
|
||||
"((/(?P<bar>.+))?))")""");
|
||||
}
|
||||
|
||||
// PY-8174
|
||||
public void testChangeSignatureKeywordAndPositionalParameters() {
|
||||
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of f(x, foo)</html>", true, true);
|
||||
}
|
||||
|
||||
// PY-8174
|
||||
public void testChangeSignatureAddKeywordOnlyParameter() {
|
||||
runWithLanguageLevel(
|
||||
LanguageLevel.PYTHON34,
|
||||
() -> doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(x, *args, foo)</html>", true, true)
|
||||
);
|
||||
}
|
||||
|
||||
// PY-8174
|
||||
public void testChangeSignatureNewParametersNames() {
|
||||
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(i1)</html>", true, true);
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testChangeSignatureOfExportedBoundMethod() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
|
||||
doMultiFilesInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of method(self, a, b)</html>", "mod.py");
|
||||
});
|
||||
}
|
||||
|
||||
// PY-8174
|
||||
public void testChangeSignatureParametersDefaultValues() {
|
||||
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func()</html>", true, true);
|
||||
}
|
||||
|
||||
public void testAddKwargsToNewMethodIncompatibleWithInit() {
|
||||
doInspectionTest(PyInitNewSignatureInspection.class, "<html>Change the signature of __new__(cls)</html>", true, true);
|
||||
}
|
||||
|
||||
public void testAddKwargsToIncompatibleOverridingMethod() {
|
||||
doInspectionTest(PyMethodOverridingInspection.class, "<html>Change the signature of m(self)</html>", true, true);
|
||||
}
|
||||
|
||||
// PY-30789
|
||||
public void testSetImportedABCMetaAsMetaclassPy2() {
|
||||
doInspectionTest("PyAbstractClassInspection/quickFix/SetImportedABCMetaAsMetaclassPy2/main.py",
|
||||
PyAbstractClassInspection.class,
|
||||
"Set '" + PyNames.ABC_META + "' as metaclass",
|
||||
true,
|
||||
true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNls
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/inspections/";
|
||||
}
|
||||
|
||||
private void doInspectionTest(@NotNull Class<? extends LocalInspectionTool> inspectionClass,
|
||||
@NotNull String quickFixName,
|
||||
boolean applyFix,
|
||||
boolean available) {
|
||||
doInspectionTest(getTestName(false) + ".py", inspectionClass, quickFixName, applyFix, available);
|
||||
}
|
||||
|
||||
protected void doInspectionTest(@TestDataFile @NonNls @NotNull String testFileName,
|
||||
@NotNull Class<? extends LocalInspectionTool> inspectionClass,
|
||||
@NonNls @NotNull String quickFixName,
|
||||
boolean applyFix,
|
||||
boolean available) {
|
||||
doInspectionTest(new String[]{testFileName}, inspectionClass, quickFixName, applyFix, available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs daemon passes and looks for given fix within infos.
|
||||
*
|
||||
* @param testFiles names of files to participate; first is used for inspection and then for check by "_after".
|
||||
* @param inspectionClass what inspection to run
|
||||
* @param quickFixName how the resulting fix should be named (the human-readable name users see)
|
||||
* @param applyFix true if the fix needs to be applied
|
||||
* @param available true if the fix should be available, false if it should be explicitly not available.
|
||||
*/
|
||||
protected void doInspectionTest(@NonNls String @NotNull [] testFiles,
|
||||
@NotNull Class<? extends LocalInspectionTool> inspectionClass,
|
||||
@NonNls @NotNull String quickFixName,
|
||||
boolean applyFix,
|
||||
boolean available) {
|
||||
myFixture.enableInspections(inspectionClass);
|
||||
myFixture.configureByFiles(testFiles);
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
final List<IntentionAction> intentionActions = myFixture.filterAvailableIntentions(quickFixName);
|
||||
if (available) {
|
||||
if (intentionActions.isEmpty()) {
|
||||
final List<String> intentionNames = ContainerUtil.map(myFixture.getAvailableIntentions(), IntentionAction::getText);
|
||||
throw new AssertionError("Quickfix starting with \"" + quickFixName + "\" is not available. " +
|
||||
"Available intentions:\n" + StringUtil.join(intentionNames, "\n"));
|
||||
}
|
||||
if (intentionActions.size() > 1) {
|
||||
throw new AssertionError("There are more than one quickfix with the name \"" + quickFixName + "\"");
|
||||
}
|
||||
if (applyFix) {
|
||||
myFixture.launchAction(intentionActions.get(0));
|
||||
NonBlockingReadActionImpl.waitForAsyncTaskCompletion();
|
||||
myFixture.checkResultByFile(graftBeforeExt(testFiles[0], "_after"), true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
assertEmpty("Quick fix \"" + quickFixName + "\" should not be available", intentionActions);
|
||||
}
|
||||
}
|
||||
|
||||
private void doMultiFilesInspectionTest(@NotNull Class<? extends LocalInspectionTool> inspectionClass,
|
||||
@NotNull String intentionStr,
|
||||
@NotNull String modifiedFile) {
|
||||
myFixture.enableInspections(inspectionClass);
|
||||
myFixture.copyDirectoryToProject(getTestName(true), "");
|
||||
myFixture.configureFromTempProjectFile(getTestName(true) + ".py");
|
||||
myFixture.checkHighlighting(true, false, false);
|
||||
final IntentionAction intentionAction = myFixture.findSingleIntention(intentionStr);
|
||||
assertNotNull(intentionAction);
|
||||
myFixture.launchAction(intentionAction);
|
||||
final String expectedFile = getTestName(true) + "/" + graftBeforeExt(modifiedFile, "_after");
|
||||
myFixture.checkResultByFile(modifiedFile, expectedFile, true);
|
||||
}
|
||||
|
||||
// Turns "name.ext" to "name_insertion.ext"
|
||||
|
||||
@NonNls
|
||||
private static String graftBeforeExt(String name, String insertion) {
|
||||
int dotpos = name.indexOf('.');
|
||||
if (dotpos < 0) dotpos = name.length();
|
||||
return name.substring(0, dotpos) + insertion + name.substring(dotpos);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.packaging.management
|
||||
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.packaging.pip.PypiPackageCache
|
||||
import java.io.IOException
|
||||
|
||||
class TestPypiPackageCache : PypiPackageCache() {
|
||||
var testPackages: Set<String> = emptySet()
|
||||
override val packages: Set<String>
|
||||
get() {
|
||||
return testPackages
|
||||
}
|
||||
|
||||
override suspend fun reloadCache(force: Boolean): Result<Unit, IOException> {
|
||||
return Result.success(Unit)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user