From 9d56716897cca3187313992415e2328280f1ac0b Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Tue, 19 Jul 2016 18:44:33 +0300 Subject: [PATCH] #KT-9009 fixed --- .../kotlin/idea/KotlinBundle.properties | 1 + .../kotlin/idea/core/KotlinIndicesHelper.kt | 35 ++- .../idea/quickfix/AutoImportStaticFix.kt | 208 ++++++++++++++++++ .../kotlin/idea/quickfix/QuickFixRegistrar.kt | 16 +- 4 files changed, 248 insertions(+), 12 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportStaticFix.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties index d8cc16a7bd9..bfbb858fef4 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinBundle.properties @@ -16,6 +16,7 @@ remove.unnecessary.non.null.assertion=Remove unnecessary non-null assertion (!!) change.to.backing.field=Change reference to backing field implement.members=Implement members import.fix=Import +import.static.fix=Static import imports.chooser.title=Imports rename.kotlin.package.class.error=Can't rename Kotlin package facade class add.semicolon.after.invocation=Add semicolon after invocation of ''{0}'' diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt index 652092bfde5..6b6aaf72214 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/KotlinIndicesHelper.kt @@ -19,19 +19,18 @@ package org.jetbrains.kotlin.idea.core import com.intellij.codeInsight.JavaProjectCodeInsightSettings import com.intellij.openapi.progress.ProgressManager import com.intellij.psi.PsiFile +import com.intellij.psi.PsiMember import com.intellij.psi.PsiModifier import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.stubs.StringStubIndexExtension import com.intellij.util.indexing.IdFilter import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.idea.caches.resolve.getJavaFieldDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference -import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.* import org.jetbrains.kotlin.idea.core.extension.KotlinIndicesHelperExtension import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.stubindex.* import org.jetbrains.kotlin.idea.util.CallType import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver @@ -154,7 +153,7 @@ class KotlinIndicesHelper( .filter { ProgressManager.checkCanceled() KotlinTopLevelExtensionsByReceiverTypeIndex.receiverTypeNameFromKey(it) in receiverTypeNames - && nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it)) + && nameFilter(KotlinTopLevelExtensionsByReceiverTypeIndex.callableNameFromKey(it)) } .flatMap { index.get(it, project, scope).asSequence() } @@ -206,6 +205,32 @@ class KotlinIndicesHelper( .toSet() } + + fun getJvmStatics(name: String): Collection { + return sequenceOf(PsiShortNamesCache.getInstance(project).getFieldsByName(name, scope), + PsiShortNamesCache.getInstance(project).getMethodsByName(name, scope)) + .flatMap { it.asSequence() } + .mapNotNull { (it as PsiMember).getJavaMemberDescriptor(resolutionFacade) } + .filter(descriptorFilter) + .toSet() + } + + fun getKotlinStatics(name: String): Collection { + return sequenceOf(KotlinFunctionShortNameIndex.getInstance().get(name, project, scope), + KotlinPropertyShortNameIndex.getInstance().get(name, project, scope)). + flatMap { it.asSequence() } + .mapNotNull { + when (it) { + is KtNamedFunction -> it.descriptor + is KtProperty -> it.descriptor + else -> null + } + } + .filter(descriptorFilter) + .toSet() + } + + fun getKotlinClasses(nameFilter: (String) -> Boolean, kindFilter: (ClassKind) -> Boolean): Collection { return KotlinFullClassNameIndex.getInstance().getAllKeys(project).asSequence() .map { FqName(it) } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportStaticFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportStaticFix.kt new file mode 100644 index 00000000000..610e31a5469 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/AutoImportStaticFix.kt @@ -0,0 +1,208 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.quickfix + +import com.intellij.codeInsight.hint.HintManager +import com.intellij.codeInsight.intention.HighPriorityAction +import com.intellij.codeInspection.HintAction +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.packageDependencies.DependencyValidationManager +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiModificationTracker +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.DiagnosticFactory +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.actions.KotlinAddImportAction +import org.jetbrains.kotlin.idea.actions.createSingleImportAction +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.caches.resolve.getResolveScope +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.core.KotlinIndicesHelper +import org.jetbrains.kotlin.idea.core.isVisible +import org.jetbrains.kotlin.idea.project.ProjectStructureUtil +import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isImportDirectiveExpression +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.expressions.OperatorConventions +import org.jetbrains.kotlin.utils.CachedValueProperty +import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptyList +import java.util.* + +/** + * Created by Semoro on 08.07.16. + * ©XCodersTeam, 2016 + */ + +class AutoStaticImportFix(expression: KtSimpleNameExpression) : + KotlinQuickFixAction(expression), HighPriorityAction, HintAction { + + private val modificationCountOnCreate = PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount + + private val suggestionCount: Int by CachedValueProperty( + calculator = { computeSuggestions().size }, + timestampCalculator = { PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount } + ) + + + override fun showHint(editor: Editor): Boolean { + if (!element.isValid || isOutdated()) return false + + if (ApplicationManager.getApplication().isUnitTestMode && HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) return false + + if (suggestionCount == 0) return false + + return createAction(element.project, editor).showHint() + } + + override fun getText() = KotlinBundle.message("import.static.fix") + + override fun getFamilyName() = KotlinBundle.message("import.fix") + + override fun isAvailable(project: Project, editor: Editor?, file: PsiFile) + = super.isAvailable(project, editor, file) && suggestionCount > 0 + + override fun invoke(project: Project, editor: Editor?, file: KtFile) { + CommandProcessor.getInstance().runUndoTransparentAction { + createAction(project, editor!!).execute() + } + } + + override fun startInWriteAction() = true + + private fun isOutdated() = modificationCountOnCreate != PsiModificationTracker.SERVICE.getInstance(element.project).modificationCount + + private fun createAction(project: Project, editor: Editor): KotlinAddImportAction { + return createSingleImportAction(project, editor, element, computeSuggestions()) + } + + fun computeSuggestions(): Collection { + if (!element.isValid) return listOf() + if (element.containingFile !is KtFile) return emptyList() + + val callTypeAndReceiver = getCallTypeAndReceiver() + + if (callTypeAndReceiver is CallTypeAndReceiver.UNKNOWN) return emptyList() + + if (importNames.isEmpty()) return emptyList() + + return importNames.flatMapTo(LinkedHashSet()) { + computeSuggestionsForName(it, callTypeAndReceiver) + } + } + + fun computeSuggestionsForName(name: Name, callTypeAndReceiver: CallTypeAndReceiver<*, *>): + Collection { + val nameStr = name.asString() + if (nameStr.isEmpty()) return emptyList() + + val file = element.containingFile as KtFile + + fun filterByCallType(descriptor: DeclarationDescriptor) = callTypeAndReceiver.callType.descriptorKindFilter.accepts(descriptor) + + val searchScope = getResolveScope(file) + + val bindingContext = element.analyze(BodyResolveMode.PARTIAL) + + val diagnostics = bindingContext.diagnostics.forElement(element) + + if (!diagnostics.any { it.factory in getSupportedErrors() }) return emptyList() + + val resolutionFacade = element.getResolutionFacade() + + fun isVisible(descriptor: DeclarationDescriptor): Boolean { + if (descriptor is DeclarationDescriptorWithVisibility) { + return descriptor.isVisible(element, callTypeAndReceiver.receiver as? KtExpression, bindingContext, resolutionFacade) + } + + return true + } + + val result = ArrayList() + + val indicesHelper = KotlinIndicesHelper(resolutionFacade, searchScope, ::isVisible) + + val expression = element + if (expression is KtSimpleNameExpression) { + if (!expression.isImportDirectiveExpression() && !KtPsiUtil.isSelectorInQualified(expression)) { + indicesHelper.getKotlinStatics(nameStr).filterTo(result, ::filterByCallType) + if (!(ProjectStructureUtil.isJsKotlinModule(file))) { + indicesHelper.getJvmStatics(nameStr).filterTo(result, ::filterByCallType) + } + } + } + return if (result.size > 1) + reduceCandidatesBasedOnDependencyRuleViolation(result, file) + else + result + } + + private fun reduceCandidatesBasedOnDependencyRuleViolation( + candidates: Collection, file: PsiFile): Collection { + val project = file.project + val validationManager = DependencyValidationManager.getInstance(project) + return candidates.filter { + val targetFile = DescriptorToSourceUtilsIde.getAnyDeclaration(project, it)?.containingFile ?: return@filter true + validationManager.getViolatorDependencyRules(file, targetFile).isEmpty() + } + } + + fun getCallTypeAndReceiver() = CallTypeAndReceiver.detect(element) + + val importNames: Collection = run { + if (element.getIdentifier() == null) { + val conventionName = KtPsiUtil.getConventionName(element) + if (conventionName != null) { + if (element is KtOperationReferenceExpression) { + val elementType = element.firstChild.node.elementType + if (elementType in OperatorConventions.ASSIGNMENT_OPERATIONS) { + val counterpart = OperatorConventions.ASSIGNMENT_OPERATION_COUNTERPARTS[elementType] + val counterpartName = OperatorConventions.BINARY_OPERATION_NAMES[counterpart] + if (counterpartName != null) { + return@run listOf(conventionName, counterpartName) + } + } + } + + return@run conventionName.singletonOrEmptyList() + } + } + else if (Name.isValidIdentifier(element.getReferencedName())) { + return@run Name.identifier(element.getReferencedName()).singletonOrEmptyList() + } + + emptyList() + } + + fun getSupportedErrors(): Collection> = ERRORS + + companion object : KotlinSingleIntentionActionFactory() { + override fun createAction(diagnostic: Diagnostic) = + (diagnostic.psiElement as? KtSimpleNameExpression)?.let { AutoStaticImportFix(it) } + + override fun isApplicableForCodeFragment() = true + + private val ERRORS: Collection> by lazy(LazyThreadSafetyMode.PUBLICATION) { QuickFixes.getInstance().getDiagnostics(this) } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt index 9cdc6e79f2e..6fb60262a31 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/QuickFixRegistrar.kt @@ -87,9 +87,9 @@ class QuickFixRegistrar : QuickFixContributor { NON_MEMBER_FUNCTION_NO_BODY.registerFactory(AddFunctionBodyFix) - NOTHING_TO_OVERRIDE.registerFactory( RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD), - ChangeMemberFunctionSignatureFix, - AddFunctionToSupertypeFix) + NOTHING_TO_OVERRIDE.registerFactory(RemoveModifierFix.createRemoveModifierFromListOwnerFactory(OVERRIDE_KEYWORD), + ChangeMemberFunctionSignatureFix, + AddFunctionToSupertypeFix) VIRTUAL_MEMBER_HIDDEN.registerFactory(AddModifierFix.createFactory(OVERRIDE_KEYWORD)) USELESS_CAST.registerFactory(RemoveUselessCastFix) @@ -128,7 +128,9 @@ class QuickFixRegistrar : QuickFixContributor { NON_PRIVATE_CONSTRUCTOR_IN_ENUM.registerFactory(removeModifierFactory) NON_PRIVATE_CONSTRUCTOR_IN_SEALED.registerFactory(removeModifierFactory) + UNRESOLVED_REFERENCE.registerFactory(AutoStaticImportFix) UNRESOLVED_REFERENCE.registerFactory(AutoImportFix) + UNRESOLVED_REFERENCE.registerFactory(AddTestLibQuickFix) UNRESOLVED_REFERENCE_WRONG_RECEIVER.registerFactory(AutoImportFix) @@ -337,8 +339,8 @@ class QuickFixRegistrar : QuickFixContributor { DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE.registerFactory(CreatePropertyDelegateAccessorsActionFactory) UNRESOLVED_REFERENCE.registerFactory(CreateClassFromTypeReferenceActionFactory, - CreateClassFromReferenceExpressionActionFactory, - CreateClassFromCallWithConstructorCalleeActionFactory, + CreateClassFromReferenceExpressionActionFactory, + CreateClassFromCallWithConstructorCalleeActionFactory, PlatformUnresolvedProvider) PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED.registerFactory(InsertDelegationCallQuickfix.InsertThisDelegationCallFactory) @@ -347,12 +349,12 @@ class QuickFixRegistrar : QuickFixContributor { EXPLICIT_DELEGATION_CALL_REQUIRED.registerFactory(InsertDelegationCallQuickfix.InsertSuperDelegationCallFactory) MISSING_CONSTRUCTOR_KEYWORD.registerFactory(MissingConstructorKeywordFix, - MissingConstructorKeywordFix.createWholeProjectFixFactory()) + MissingConstructorKeywordFix.createWholeProjectFixFactory()) ANONYMOUS_FUNCTION_WITH_NAME.registerFactory(RemoveNameFromFunctionExpressionFix) UNRESOLVED_REFERENCE.registerFactory(ReplaceObsoleteLabelSyntaxFix, - ReplaceObsoleteLabelSyntaxFix.createWholeProjectFixFactory()) + ReplaceObsoleteLabelSyntaxFix.createWholeProjectFixFactory()) DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix) DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)