diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/MockExternalAnnotationsManager.kt.191 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/MockExternalAnnotationsManager.kt.191 new file mode 100644 index 00000000000..ae5ffcb91b8 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/MockExternalAnnotationsManager.kt.191 @@ -0,0 +1,69 @@ +/* + * 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.cli.jvm.compiler + +import com.intellij.codeInsight.ExternalAnnotationsManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.* + +class MockExternalAnnotationsManager : ExternalAnnotationsManager() { + override fun chooseAnnotationsPlace(element: PsiElement): AnnotationPlace? = null + + override fun isExternalAnnotationWritable(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean = false + override fun isExternalAnnotation(annotation: PsiAnnotation): Boolean = false + + override fun findExternalAnnotationsFiles(listOwner: PsiModifierListOwner): List? = null + override fun findExternalAnnotation(listOwner: PsiModifierListOwner, annotationFQN: String): PsiAnnotation? = null + override fun findExternalAnnotations(listOwner: PsiModifierListOwner): Array? = null + + override fun annotateExternally( + listOwner: PsiModifierListOwner, + annotationFQName: String, + fromFile: PsiFile, + value: Array? + ) { + throw UnsupportedOperationException("not implemented") + } + + override fun deannotate(listOwner: PsiModifierListOwner, annotationFQN: String): Boolean { + throw UnsupportedOperationException("not implemented") + } + + override fun editExternalAnnotation( + listOwner: PsiModifierListOwner, + annotationFQN: String, + value: Array? + ): Boolean { + throw UnsupportedOperationException("not implemented") + } + + override fun hasAnnotationRootsForFile(file: VirtualFile): Boolean { + throw UnsupportedOperationException("not implemented") + } + + override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass, annotationFQN: String): MutableList { + throw UnsupportedOperationException("not implemented") + } + + override fun findDefaultConstructorExternalAnnotations(aClass: PsiClass): MutableList { + throw UnsupportedOperationException("not implemented") + } + + override fun findExternalAnnotations(listOwner: PsiModifierListOwner, annotationFQN: String): MutableList { + throw UnsupportedOperationException("not implemented") + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt.191 b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt.191 new file mode 100644 index 00000000000..00ea1bd6ad6 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveKotlinDeclarationsProcessor.kt.191 @@ -0,0 +1,373 @@ +/* + * 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.refactoring.move.moveDeclarations + +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.ide.util.EditorHelper +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Ref +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.PsiReference +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.BaseRefactoringProcessor +import com.intellij.refactoring.move.MoveCallback +import com.intellij.refactoring.move.MoveMultipleElementsViewDescriptor +import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassHandler +import com.intellij.refactoring.rename.RenameUtil +import com.intellij.refactoring.util.NonCodeUsageInfo +import com.intellij.refactoring.util.RefactoringUIUtil +import com.intellij.refactoring.util.TextOccurrencesUtil +import com.intellij.usageView.UsageInfo +import com.intellij.usageView.UsageViewBundle +import com.intellij.usageView.UsageViewDescriptor +import com.intellij.usageView.UsageViewUtil +import com.intellij.util.IncorrectOperationException +import com.intellij.util.containers.MultiMap +import gnu.trove.THashMap +import gnu.trove.TObjectHashingStrategy +import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration +import org.jetbrains.kotlin.asJava.findFacadeClass +import org.jetbrains.kotlin.asJava.namedUnwrappedElement +import org.jetbrains.kotlin.asJava.toLightElements +import org.jetbrains.kotlin.idea.codeInsight.shorten.addToBeShortenedDescendantsToWaitingSet +import org.jetbrains.kotlin.idea.core.deleteSingle +import org.jetbrains.kotlin.idea.core.quoteIfNeeded +import org.jetbrains.kotlin.idea.refactoring.broadcastRefactoringExit +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.idea.refactoring.move.* +import org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinClassHandler +import org.jetbrains.kotlin.idea.search.projectScope +import org.jetbrains.kotlin.idea.search.restrictByFileType +import org.jetbrains.kotlin.idea.util.projectStructure.module +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext +import org.jetbrains.kotlin.psi.psiUtil.isAncestor +import org.jetbrains.kotlin.utils.ifEmpty +import org.jetbrains.kotlin.utils.keysToMap +import java.lang.AssertionError +import java.util.* + +interface Mover : (KtNamedDeclaration, KtElement) -> KtNamedDeclaration { + object Default : Mover { + override fun invoke(originalElement: KtNamedDeclaration, targetContainer: KtElement): KtNamedDeclaration { + return when (targetContainer) { + is KtFile -> { + val declarationContainer: KtElement = + if (targetContainer.isScript()) targetContainer.script!!.blockExpression else targetContainer + declarationContainer.add(originalElement) as KtNamedDeclaration + } + is KtClassOrObject -> targetContainer.addDeclaration(originalElement) + else -> error("Unexpected element: ${targetContainer.getElementTextWithContext()}") + }.apply { + val container = originalElement.containingClassOrObject + if (container is KtObjectDeclaration && container.isCompanion() && container.declarations.singleOrNull() == originalElement) { + container.deleteSingle() + } + else { + originalElement.deleteSingle() + } + } + } + } +} + +sealed class MoveSource { + abstract val elementsToMove: Collection + + class Elements(override val elementsToMove: Collection) : MoveSource() + + class File(val file: KtFile) : MoveSource() { + override val elementsToMove: Collection + get() = file.declarations.filterIsInstance() + } +} + +fun MoveSource(declaration: KtNamedDeclaration) = MoveSource.Elements(listOf(declaration)) +fun MoveSource(declarations: Collection) = MoveSource.Elements(declarations) +fun MoveSource(file: KtFile) = MoveSource.File(file) + +class MoveDeclarationsDescriptor @JvmOverloads constructor( + val project: Project, + val moveSource: MoveSource, + val moveTarget: KotlinMoveTarget, + val delegate: MoveDeclarationsDelegate, + val searchInCommentsAndStrings: Boolean = true, + val searchInNonCode: Boolean = true, + val deleteSourceFiles: Boolean = false, + val moveCallback: MoveCallback? = null, + val openInEditor: Boolean = false, + val allElementsToMove: List? = null, + val analyzeConflicts: Boolean = true, + val searchReferences: Boolean = true +) + +class ConflictUsageInfo(element: PsiElement, val messages: Collection) : UsageInfo(element) + +private object ElementHashingStrategy : TObjectHashingStrategy { + override fun equals(e1: PsiElement?, e2: PsiElement?): Boolean { + if (e1 === e2) return true + // Name should be enough to distinguish different light elements based on the same original declaration + if (e1 is KtLightDeclaration<*, *> && e2 is KtLightDeclaration<*, *>) { + return e1.kotlinOrigin == e2.kotlinOrigin && e1.name == e2.name + } + return e1 == e2 + } + + override fun computeHashCode(e: PsiElement?): Int { + return when (e) { + null -> 0 + is KtLightDeclaration<*, *> -> (e.kotlinOrigin?.hashCode() ?: 0) * 31 + (e.name?.hashCode() ?: 0) + else -> e.hashCode() + } + } +} + +class MoveKotlinDeclarationsProcessor( + val descriptor: MoveDeclarationsDescriptor, + val mover: Mover = Mover.Default) : BaseRefactoringProcessor(descriptor.project) { + companion object { + private val REFACTORING_NAME = "Move declarations" + val REFACTORING_ID = "move.kotlin.declarations" + } + + val project get() = descriptor.project + + private var nonCodeUsages: Array? = null + private val moveEntireFile = descriptor.moveSource is MoveSource.File + private val elementsToMove = descriptor.moveSource.elementsToMove.filter { e -> e.parent != descriptor.moveTarget.getTargetPsiIfExists(e) } + private val kotlinToLightElementsBySourceFile = elementsToMove + .groupBy { it.containingKtFile } + .mapValues { it.value.keysToMap { it.toLightElements().ifEmpty { listOf(it) } } } + private val conflicts = MultiMap() + + override fun getRefactoringId() = REFACTORING_ID + + override fun createUsageViewDescriptor(usages: Array): UsageViewDescriptor { + val targetContainerFqName = descriptor.moveTarget.targetContainerFqName?.let { + if (it.isRoot) UsageViewBundle.message("default.package.presentable.name") else it.asString() + } + return MoveMultipleElementsViewDescriptor(elementsToMove.toTypedArray(), targetContainerFqName) + } + + fun getConflictsAsUsages(): List = conflicts.entrySet().map { ConflictUsageInfo(it.key, it.value) } + + public override fun findUsages(): Array { + if (!descriptor.searchReferences || elementsToMove.isEmpty()) return UsageInfo.EMPTY_ARRAY + + val newContainerName = descriptor.moveTarget.targetContainerFqName?.asString() ?: "" + + fun getSearchScope(element: PsiElement): GlobalSearchScope? { + val projectScope = project.projectScope() + val ktDeclaration = element.namedUnwrappedElement as? KtNamedDeclaration ?: return projectScope + if (ktDeclaration.hasModifier(KtTokens.PRIVATE_KEYWORD)) return projectScope + val moveTarget = descriptor.moveTarget + val (oldContainer, newContainer) = descriptor.delegate.getContainerChangeInfo(ktDeclaration, moveTarget) + val targetModule = moveTarget.getTargetModule(project) ?: return projectScope + if (oldContainer != newContainer || ktDeclaration.module != targetModule) return projectScope + // Check if facade class may change + if (newContainer is ContainerInfo.Package) { + val javaScope = projectScope.restrictByFileType(JavaFileType.INSTANCE) + val currentFile = ktDeclaration.containingKtFile + val newFile = when (moveTarget) { + is KotlinMoveTargetForExistingElement -> moveTarget.targetElement as? KtFile ?: return null + is KotlinMoveTargetForDeferredFile -> return javaScope + else -> return null + } + val currentFacade = currentFile.findFacadeClass() + val newFacade = newFile.findFacadeClass() + return if (currentFacade?.qualifiedName != newFacade?.qualifiedName) javaScope else null + } + return null + } + + fun collectUsages(kotlinToLightElements: Map>, result: MutableCollection) { + kotlinToLightElements.values.flatten().flatMapTo(result) { lightElement -> + val searchScope = getSearchScope(lightElement) ?: return@flatMapTo emptyList() + val elementName = lightElement.name ?: return@flatMapTo emptyList() + val newFqName = StringUtil.getQualifiedName(newContainerName, elementName) + + val foundReferences = HashSet() + val results = ReferencesSearch + .search(lightElement, searchScope) + .mapNotNullTo(ArrayList()) { ref -> + if (foundReferences.add(ref) && elementsToMove.none { it.isAncestor(ref.element)}) { + createMoveUsageInfoIfPossible(ref, lightElement, true, false) + } + else null + } + + val name = lightElement.getKotlinFqName()?.quoteIfNeeded()?.asString() + if (name != null) { + TextOccurrencesUtil.findNonCodeUsages( + lightElement, + name, + descriptor.searchInCommentsAndStrings, + descriptor.searchInNonCode, + FqName(newFqName).quoteIfNeeded().asString(), + results + ) + } + + MoveClassHandler.EP_NAME.extensions.forEach { handler -> + if (handler !is MoveKotlinClassHandler) handler.preprocessUsages(results) + } + + results + } + } + + val usages = ArrayList() + val conflictChecker = MoveConflictChecker( + project, + elementsToMove, + descriptor.moveTarget, + elementsToMove.first(), + allElementsToMove = descriptor.allElementsToMove + ) + for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) { + val internalUsages = LinkedHashSet() + val externalUsages = LinkedHashSet() + + if (moveEntireFile) { + val changeInfo = ContainerChangeInfo( + ContainerInfo.Package(sourceFile.packageFqName), + descriptor.moveTarget.targetContainerFqName?.let { ContainerInfo.Package(it) } ?: ContainerInfo.UnknownPackage + ) + internalUsages += sourceFile.getInternalReferencesToUpdateOnPackageNameChange(changeInfo) + } + else { + kotlinToLightElements.keys.forEach { + val packageNameInfo = descriptor.delegate.getContainerChangeInfo(it, descriptor.moveTarget) + internalUsages += it.getInternalReferencesToUpdateOnPackageNameChange(packageNameInfo) + } + } + + internalUsages += descriptor.delegate.findInternalUsages(descriptor) + collectUsages(kotlinToLightElements, externalUsages) + if (descriptor.analyzeConflicts) { + conflictChecker.checkAllConflicts(externalUsages, internalUsages, conflicts) + descriptor.delegate.collectConflicts(descriptor, internalUsages, conflicts) + } + + usages += internalUsages + usages += externalUsages + } + + return UsageViewUtil.removeDuplicatedUsages(usages.toTypedArray()) + } + + override fun preprocessUsages(refUsages: Ref>): Boolean { + return showConflicts(conflicts, refUsages.get()) + } + + override fun performRefactoring(usages: Array) = doPerformRefactoring(usages.toList()) + + internal fun doPerformRefactoring(usages: List) { + fun moveDeclaration(declaration: KtNamedDeclaration, moveTarget: KotlinMoveTarget): KtNamedDeclaration { + val targetContainer = moveTarget.getOrCreateTargetPsi(declaration) + ?: throw AssertionError("Couldn't create Kotlin file for: ${declaration::class.java}: ${declaration.text}") + descriptor.delegate.preprocessDeclaration(descriptor, declaration) + if (moveEntireFile) return declaration + return mover(declaration, targetContainer).apply { + addToBeShortenedDescendantsToWaitingSet() + } + } + + val (oldInternalUsages, externalUsages) = usages.partition { it is KotlinMoveUsage && it.isInternal } + val newInternalUsages = ArrayList() + + markInternalUsages(oldInternalUsages) + + val usagesToProcess = ArrayList(externalUsages) + + try { + descriptor.delegate.preprocessUsages(descriptor, usages) + + val oldToNewElementsMapping = THashMap(ElementHashingStrategy) + + val newDeclarations = ArrayList() + + for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) { + for ((oldDeclaration, oldLightElements) in kotlinToLightElements) { + val elementListener = transaction?.getElementListener(oldDeclaration) + + val newDeclaration = moveDeclaration(oldDeclaration, descriptor.moveTarget) + newDeclarations += newDeclaration + + oldToNewElementsMapping[oldDeclaration] = newDeclaration + oldToNewElementsMapping[sourceFile] = newDeclaration.containingKtFile + + elementListener?.elementMoved(newDeclaration) + for ((oldElement, newElement) in oldLightElements.asSequence().zip(newDeclaration.toLightElements().asSequence())) { + oldToNewElementsMapping[oldElement] = newElement + } + + if (descriptor.openInEditor) { + EditorHelper.openInEditor(newDeclaration) + } + } + + if (descriptor.deleteSourceFiles) { + sourceFile.delete() + } + } + + val internalUsageScopes: List = if (moveEntireFile) { + newDeclarations.asSequence().map { it.containingKtFile }.distinct().toList() + } else { + newDeclarations + } + internalUsageScopes.forEach { newInternalUsages += restoreInternalUsages(it, oldToNewElementsMapping) } + + usagesToProcess += newInternalUsages + + nonCodeUsages = postProcessMoveUsages(usagesToProcess, oldToNewElementsMapping).toTypedArray() + } + catch (e: IncorrectOperationException) { + nonCodeUsages = null + RefactoringUIUtil.processIncorrectOperation(myProject, e) + } + finally { + cleanUpInternalUsages(newInternalUsages + oldInternalUsages) + } + } + + override fun performPsiSpoilingRefactoring() { + nonCodeUsages?.let { nonCodeUsages -> RenameUtil.renameNonCodeUsages(myProject, nonCodeUsages) } + descriptor.moveCallback?.refactoringCompleted() + } + + fun execute(usages: List) { + execute(usages.toTypedArray()) + } + + override fun doRun() { + try { + super.doRun() + } finally { + broadcastRefactoringExit(myProject, refactoringId) + } + } + + override fun getCommandName(): String = REFACTORING_NAME +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.191 b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.191 new file mode 100644 index 00000000000..00e2f45d215 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinTargetElementEvaluator.kt.191 @@ -0,0 +1,122 @@ +/* + * Copyright 2010-2015 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.search.ideaExtensions + +import com.intellij.codeInsight.JavaTargetElementEvaluator +import com.intellij.codeInsight.TargetElementEvaluatorEx +import com.intellij.codeInsight.TargetElementUtil +import com.intellij.codeInsight.TargetElementUtilExtender +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiReference +import com.intellij.util.BitUtil +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.idea.intentions.isAutoCreatedItUsage +import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference +import org.jetbrains.kotlin.idea.references.KtSimpleNameReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.source.getPsi + +class KotlinTargetElementEvaluator : TargetElementEvaluatorEx, TargetElementUtilExtender { + companion object { + const val DO_NOT_UNWRAP_LABELED_EXPRESSION = 0x100 + const val BYPASS_IMPORT_ALIAS = 0x200 + + // Place caret after the open curly brace in lambda for generated 'it' + fun findLambdaOpenLBraceForGeneratedIt(ref: PsiReference): PsiElement? { + val element: PsiElement = ref.element + if (element.text != "it") return null + + if (element !is KtNameReferenceExpression || !isAutoCreatedItUsage(element)) return null + + val itDescriptor = element.resolveMainReferenceToDescriptors().singleOrNull() ?: return null + val descriptorWithSource = itDescriptor.containingDeclaration as? DeclarationDescriptorWithSource ?: return null + val lambdaExpression = descriptorWithSource.source.getPsi()?.parent as? KtLambdaExpression ?: return null + return lambdaExpression.leftCurlyBrace.treeNext?.psi + } + + // Navigate to receiver element for this in extension declaration + fun findReceiverForThisInExtensionFunction(ref: PsiReference): PsiElement? { + val element: PsiElement = ref.element + if (element.text != "this") return null + + if (element !is KtNameReferenceExpression) return null + val callableDescriptor = element.resolveMainReferenceToDescriptors().singleOrNull() as? CallableDescriptor ?: return null + + if (!callableDescriptor.isExtension) return null + val callableDeclaration = callableDescriptor.source.getPsi() as? KtCallableDeclaration ?: return null + + return callableDeclaration.receiverTypeReference + } + } + + override fun getAdditionalDefinitionSearchFlags() = 0 + + override fun getAdditionalReferenceSearchFlags() = DO_NOT_UNWRAP_LABELED_EXPRESSION or BYPASS_IMPORT_ALIAS + + override fun getAllAdditionalFlags() = additionalDefinitionSearchFlags + additionalReferenceSearchFlags + + override fun includeSelfInGotoImplementation(element: PsiElement): Boolean = !(element is KtClass && element.isAbstract()) + + override fun getElementByReference(ref: PsiReference, flags: Int): PsiElement? { + if (ref is KtSimpleNameReference && ref.expression is KtLabelReferenceExpression) { + val refTarget = ref.resolve() as? KtExpression ?: return null + if (!BitUtil.isSet(flags, DO_NOT_UNWRAP_LABELED_EXPRESSION)) { + return refTarget.getLabeledParent(ref.expression.getReferencedName()) ?: refTarget + } + return refTarget + } + + if (!BitUtil.isSet(flags, BYPASS_IMPORT_ALIAS)) { + (ref.element as? KtSimpleNameExpression)?.mainReference?.getImportAlias()?.let { return it } + } + + // prefer destructing declaration entry to its target if element name is accepted + if (ref is KtDestructuringDeclarationReference && BitUtil.isSet(flags, TargetElementUtil.ELEMENT_NAME_ACCEPTED)) { + return ref.element + } + + val refExpression = ref.element as? KtSimpleNameExpression + val calleeExpression = refExpression?.getParentOfTypeAndBranch { calleeExpression } + if (calleeExpression != null) { + (ref.resolve() as? KtConstructor<*>)?.let { + return if (flags and JavaTargetElementEvaluator().additionalReferenceSearchFlags != 0) it else it.containingClassOrObject + } + } + + if (BitUtil.isSet(flags, TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED)) { + return findLambdaOpenLBraceForGeneratedIt(ref) + ?: findReceiverForThisInExtensionFunction(ref) + } + + return null + } + + override fun isIdentifierPart(file: PsiFile, text: CharSequence?, offset: Int): Boolean { + val elementAtCaret = file.findElementAt(offset) + + if (elementAtCaret?.node?.elementType == KtTokens.IDENTIFIER) return true + // '(' is considered identifier part if it belongs to primary constructor without 'constructor' keyword + return elementAtCaret?.getNonStrictParentOfType()?.textOffset == offset + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.191 b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.191 new file mode 100644 index 00000000000..c1e6d7eed68 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/slicer/KotlinSliceProvider.kt.191 @@ -0,0 +1,118 @@ +/* + * Copyright 2010-2017 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.slicer + +import com.intellij.codeInspection.dataFlow.Nullness +import com.intellij.ide.util.treeView.AbstractTreeStructure +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.psi.PsiElement +import com.intellij.slicer.* +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.guessTypes +import org.jetbrains.kotlin.idea.references.KtReference +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isPlainWithEscapes +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.isError +import org.jetbrains.kotlin.types.isNullabilityFlexible + +class KotlinSliceProvider : SliceLanguageSupportProvider, SliceUsageTransformer { + companion object { + val LEAF_ELEMENT_EQUALITY = object : SliceLeafEquality() { + override fun substituteElement(element: PsiElement) = (element as? KtReference)?.resolve() ?: element + } + } + + class KotlinGroupByNullnessAction(treeBuilder: SliceTreeBuilder) : GroupByNullnessActionBase(treeBuilder) { + override fun isAvailable() = true + } + + val leafAnalyzer by lazy { SliceLeafAnalyzer(LEAF_ELEMENT_EQUALITY, this) } + val nullnessAnalyzer: SliceNullnessAnalyzerBase by lazy { + object : SliceNullnessAnalyzerBase(LEAF_ELEMENT_EQUALITY, this) { + override fun checkNullness(element: PsiElement?): Nullness { + val types = when (element) { + is KtCallableDeclaration -> listOfNotNull((element.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType) + is KtDeclaration -> emptyList() + is KtExpression -> listOfNotNull(element.analyze(BodyResolveMode.PARTIAL).getType(element)) + else -> emptyList() + } + return when { + types.isEmpty() -> return Nullness.UNKNOWN + types.all { KotlinBuiltIns.isNullableNothing(it) } -> Nullness.NULLABLE + types.any { it.isError || TypeUtils.isNullableType(it) || it.isNullabilityFlexible() } -> Nullness.UNKNOWN + else -> Nullness.NOT_NULL + } + } + } + } + + override fun createRootUsage(element: PsiElement, params: SliceAnalysisParams) = KotlinSliceUsage(element, params) + + override fun transform(usage: SliceUsage): Collection? { + if (usage is KotlinSliceUsage) return null + return listOf(KotlinSliceUsage(usage.element, usage.parent, 0, false)) + } + + override fun getExpressionAtCaret(atCaret: PsiElement, dataFlowToThis: Boolean): KtExpression? { + val element = + atCaret.parentsWithSelf + ?.firstOrNull { + it is KtProperty || + it is KtParameter || + it is KtDeclarationWithBody || + (it is KtClass && !it.hasExplicitPrimaryConstructor()) || + (it is KtExpression && it !is KtDeclaration) + } + ?.let { KtPsiUtil.safeDeparenthesize(it as KtExpression) } ?: return null + if (dataFlowToThis) { + if (element is KtConstantExpression) return null + if (element is KtStringTemplateExpression && element.isPlainWithEscapes()) return null + if (element is KtClassLiteralExpression) return null + if (element is KtCallableReferenceExpression) return null + } + return element + } + + override fun getElementForDescription(element: PsiElement): PsiElement { + return (element as? KtSimpleNameExpression)?.mainReference?.resolve() ?: element + } + + override fun getRenderer() = KotlinSliceUsageCellRenderer + + override fun startAnalyzeLeafValues(structure: AbstractTreeStructure, finalRunnable: Runnable) { + leafAnalyzer.startAnalyzeValues(structure, finalRunnable) + } + + override fun startAnalyzeNullness(structure: AbstractTreeStructure, finalRunnable: Runnable) { + nullnessAnalyzer.startAnalyzeNullness(structure, finalRunnable) + } + + override fun registerExtraPanelActions(group: DefaultActionGroup, builder: SliceTreeBuilder) { + if (builder.dataFlowToThis) { + group.add(GroupByLeavesAction(builder)) + group.add(KotlinGroupByNullnessAction(builder)) + } + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/evaluation/KotlinEvaluatorExtension.kt.191 b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/evaluation/KotlinEvaluatorExtension.kt.191 new file mode 100644 index 00000000000..36ad4408c6b --- /dev/null +++ b/plugins/uast-kotlin/src/org/jetbrains/uast/kotlin/evaluation/KotlinEvaluatorExtension.kt.191 @@ -0,0 +1,57 @@ +package org.jetbrains.uast.kotlin.evaluation + +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.uast.UBinaryExpression +import org.jetbrains.uast.UastPostfixOperator +import org.jetbrains.uast.evaluation.AbstractEvaluatorExtension +import org.jetbrains.uast.evaluation.UEvaluationInfo +import org.jetbrains.uast.evaluation.UEvaluationState +import org.jetbrains.uast.kotlin.KotlinBinaryOperators +import org.jetbrains.uast.kotlin.KotlinPostfixOperators +import org.jetbrains.uast.values.* +import org.jetbrains.uast.evaluation.to + +class KotlinEvaluatorExtension : AbstractEvaluatorExtension(KotlinLanguage.INSTANCE) { + + private data class Range(val from: UValue, val to: UValue) { + override fun toString() = "$from..$to" + } + + private class UClosedRangeConstant(override val value: Range, override val source: UBinaryExpression?) : UAbstractConstant() { + constructor(from: UValue, to: UValue, source: UBinaryExpression): this(Range(from, to), source) + } + + override fun evaluatePostfix( + operator: UastPostfixOperator, + operandValue: UValue, + state: UEvaluationState + ): UEvaluationInfo { + return when (operator) { + KotlinPostfixOperators.EXCLEXCL -> when (operandValue.toConstant()) { + UNullConstant -> UValue.UNREACHABLE + is UConstant -> operandValue + else -> UUndeterminedValue + } to state + else -> return super.evaluatePostfix(operator, operandValue, state) + } + } + + private fun UValue.contains(value: UValue): UValue { + val range = (this as? UClosedRangeConstant)?.value ?: return UUndeterminedValue + return (value greaterOrEquals range.from) and (value lessOrEquals range.to) + } + + override fun evaluateBinary( + binaryExpression: UBinaryExpression, + leftValue: UValue, + rightValue: UValue, + state: UEvaluationState + ): UEvaluationInfo { + return when (binaryExpression.operator) { + KotlinBinaryOperators.IN -> rightValue.contains(leftValue) + KotlinBinaryOperators.NOT_IN -> !rightValue.contains(leftValue) + KotlinBinaryOperators.RANGE_TO -> UClosedRangeConstant(leftValue, rightValue, binaryExpression) + else -> UUndeterminedValue + } to state + } +} \ No newline at end of file