diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtConstructor.kt.182 b/compiler/psi/src/org/jetbrains/kotlin/psi/KtConstructor.kt.182 new file mode 100644 index 00000000000..42b2191a7f0 --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtConstructor.kt.182 @@ -0,0 +1,98 @@ +/* + * 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.psi + +import com.intellij.lang.ASTNode +import com.intellij.navigation.ItemPresentationProviders +import com.intellij.psi.PsiElement +import com.intellij.psi.search.SearchScope +import com.intellij.util.IncorrectOperationException +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub +import org.jetbrains.kotlin.psi.stubs.elements.KtPlaceHolderStubElementType +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes + +abstract class KtConstructor> : KtDeclarationStub>, KtFunction { + protected constructor(node: ASTNode) : super(node) + protected constructor(stub: KotlinPlaceHolderStub, nodeType: KtPlaceHolderStubElementType) : super(stub, nodeType) + + abstract fun getContainingClassOrObject(): KtClassOrObject + + override fun isLocal() = false + + override fun getValueParameterList() = getStubOrPsiChild(KtStubElementTypes.VALUE_PARAMETER_LIST) + + override fun getValueParameters() = valueParameterList?.parameters ?: emptyList() + + override fun getReceiverTypeReference() = null + + override fun getTypeReference() = null + + @Throws(IncorrectOperationException::class) + override fun setTypeReference(typeRef: KtTypeReference?) = throw IncorrectOperationException("setTypeReference to constructor") + + override fun getColon() = findChildByType(KtTokens.COLON) + + override fun getBodyExpression(): KtBlockExpression? = null + + override fun getEqualsToken() = null + + override fun hasBlockBody() = bodyExpression != null + + override fun hasBody() = bodyExpression != null + + override fun hasDeclaredReturnType() = false + + override fun getTypeParameterList() = null + + override fun getTypeConstraintList() = null + + override fun getTypeConstraints() = emptyList() + + override fun getTypeParameters() = emptyList() + + override fun getName(): String? = getContainingClassOrObject().name + + override fun getNameAsSafeName() = KtPsiUtil.safeName(name) + + override fun getFqName() = null + + override fun getNameAsName() = nameAsSafeName + + override fun getNameIdentifier() = null + + override fun getIdentifyingElement(): PsiElement? = getConstructorKeyword() + + @Throws(IncorrectOperationException::class) + override fun setName(name: String): PsiElement = throw IncorrectOperationException("setName to constructor") + + override fun getPresentation() = ItemPresentationProviders.getItemPresentation(this) + + open fun getConstructorKeyword(): PsiElement? = findChildByType(KtTokens.CONSTRUCTOR_KEYWORD) + + fun hasConstructorKeyword(): Boolean = stub != null || getConstructorKeyword() != null + + override fun getTextOffset(): Int { + return getConstructorKeyword()?.textOffset + ?: valueParameterList?.textOffset + ?: super.getTextOffset() + } + + override fun getUseScope(): SearchScope { + return getContainingClassOrObject().useScope + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtObjectDeclaration.kt.182 b/compiler/psi/src/org/jetbrains/kotlin/psi/KtObjectDeclaration.kt.182 new file mode 100644 index 00000000000..3bdc642d9ed --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtObjectDeclaration.kt.182 @@ -0,0 +1,73 @@ +/* + * 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.psi + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import org.jetbrains.annotations.NonNls +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.name.SpecialNames +import org.jetbrains.kotlin.psi.stubs.KotlinObjectStub +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes + +class KtObjectDeclaration : KtClassOrObject { + constructor(node: ASTNode) : super(node) + constructor(stub: KotlinObjectStub) : super(stub, KtStubElementTypes.OBJECT_DECLARATION) + + private val _stub: KotlinObjectStub? + get() = stub as? KotlinObjectStub + + override fun getName(): String? { + super.getName()?.let { return it } + + if (isCompanion() && !isTopLevel()) { + //NOTE: a hack in PSI that simplifies writing frontend code + return SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT.toString() + } + + return null + } + + override fun setName(@NonNls name: String): PsiElement { + return if (nameIdentifier == null) { + val psiFactory = KtPsiFactory(project) + val result = addAfter(psiFactory.createIdentifier(name), getObjectKeyword()!!) + addAfter(psiFactory.createWhiteSpace(), getObjectKeyword()!!) + + result + } else { + super.setName(name) + } + } + + fun isCompanion(): Boolean = _stub?.isCompanion() ?: hasModifier(KtTokens.COMPANION_KEYWORD) + + override fun getTextOffset(): Int = nameIdentifier?.textRange?.startOffset + ?: getObjectKeyword()!!.textRange.startOffset + + override fun accept(visitor: KtVisitor, data: D): R { + return visitor.visitObjectDeclaration(this, data) + } + + fun isObjectLiteral(): Boolean = _stub?.isObjectLiteral() ?: (parent is KtObjectLiteralExpression) + + fun getObjectKeyword(): PsiElement? = findChildByType(KtTokens.OBJECT_KEYWORD) + + override fun getIdentifyingElement(): PsiElement? = getObjectKeyword() + + override fun getCompanionObjects(): List = emptyList() +} diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 10d16003860..ab3c002f7c2 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2871,4 +2871,8 @@ + + + + diff --git a/idea/src/org/jetbrains/kotlin/idea/jvm/KotlinDeclarationSearcher.kt.182 b/idea/src/org/jetbrains/kotlin/idea/jvm/KotlinDeclarationSearcher.kt.182 new file mode 100644 index 00000000000..04bf78ef5cc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/jvm/KotlinDeclarationSearcher.kt.182 @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.jvm + +import com.intellij.lang.jvm.JvmElement +import com.intellij.lang.jvm.source.JvmDeclarationSearcher +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNamedElement +import org.jetbrains.kotlin.asJava.toLightElements +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtElement + +class KotlinDeclarationSearcher : JvmDeclarationSearcher { + override fun findDeclarations(declaringElement: PsiElement): Collection = + getLightElements(declaringElement).mapNotNull { it as? JvmElement } + + private fun getLightElements(declaringElement: PsiElement): List = when (declaringElement) { + is KtClass -> { + val primaryConstructor = declaringElement.primaryConstructor + if (primaryConstructor?.hasConstructorKeyword() != false) + declaringElement.toLightElements() + else + declaringElement.toLightElements() + primaryConstructor.toLightElements() + } + is KtElement -> declaringElement.toLightElements() + else -> emptyList() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/jvm/KotlinJvmDeclarationSearcherTest.kt.182 b/idea/tests/org/jetbrains/kotlin/idea/jvm/KotlinJvmDeclarationSearcherTest.kt.182 new file mode 100644 index 00000000000..da2a265284a --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/jvm/KotlinJvmDeclarationSearcherTest.kt.182 @@ -0,0 +1,175 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.jvm + +import com.intellij.lang.jvm.JvmClass +import com.intellij.lang.jvm.JvmElement +import com.intellij.lang.jvm.JvmMethod +import com.intellij.lang.jvm.JvmParameter +import com.intellij.lang.jvm.source.JvmDeclarationSearch +import com.intellij.lang.jvm.source.JvmDeclarationSearcher +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiRecursiveElementVisitor +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.psi.KtFile +import kotlin.reflect.KClass + +class KotlinJvmDeclarationSearcherTest : KotlinLightCodeInsightFixtureTestCase() { + override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE + + fun testClassWithFieldsAndMethods() = assertDeclares( + """ + + class SomeClass(val field: String) { + + lateinit var anotherField:String + + constructor(i: Int): this(i.toString()){} + + fun foo():Unit {} + + @JvmOverloads + fun bar(a: Long = 1L, b: Int = 2){} + + } + + """, + JvmDeclared("class SomeClass", JvmClass::class, JvmMethod::class), + JvmDeclared("(val field: String)", JvmMethod::class), + JvmDeclared("lateinit var anotherField", JvmMethod::class, JvmMethod::class, com.intellij.lang.jvm.JvmField::class), + JvmDeclared("val field: String", JvmParameter::class, JvmMethod::class), + JvmDeclared("constructor(i: Int)", JvmMethod::class), + JvmDeclared("i: Int", JvmParameter::class), + JvmDeclared("a: Long", JvmParameter::class, JvmParameter::class), + JvmDeclared("b: Int", JvmParameter::class), + JvmDeclared("fun foo()", JvmMethod::class), + JvmDeclared("fun bar", JvmMethod::class, JvmMethod::class, JvmMethod::class) + ) + + + fun testLocalObject() = assertDeclares( + """ /* Facade class */ + open class SomeClass(field: String) + + fun foo(){ + val obj1 = object : SomeClass("foo") + + object obj2 { + fun bar(){} + } + } + + """, + JvmDeclared("class SomeClass", JvmClass::class, JvmMethod::class), + JvmDeclared("Facade class", JvmClass::class), + JvmDeclared("(field: String)", JvmMethod::class), + JvmDeclared("field: String", JvmParameter::class), + JvmDeclared("fun foo()", JvmMethod::class), + JvmDeclared("object : SomeClass(\"foo\")", JvmClass::class), + JvmDeclared("object obj2", JvmClass::class), + JvmDeclared("fun bar", JvmMethod::class) + ) + + + fun testClassDeclaration() = assertElementsByIdentifier(""" + class SomeClass(val field: String) + """, { it is JvmClass }, { it is com.intellij.lang.jvm.JvmMethod && it.isConstructor }) + + + fun testLocalObjectDeclaration() = assertElementsByIdentifier(""" + val e = object {} + """, { it is JvmClass }) + + + fun testClassDeclarationWithConstructor() = assertElementsByIdentifier(""" + class SomeClass constructor(val field: String) + """, { it is JvmClass }) + + + fun testPrimaryConstructorByConstructorKeyword() = assertElementsByIdentifier(""" + class SomeClass constructor(val field: String) + """, { it is JvmMethod && it.isConstructor }) + + + private fun assertElementsByIdentifier(text: String, vararg matches: (JvmElement) -> Boolean) { + myFixture.configureByText("Declaraions.kt", text.trimIndent()) + val elementsByIdentifier = JvmDeclarationSearch.getElementsByIdentifier(myFixture.file.findElementAt(myFixture.caretOffset)!!) + assertMatches(elementsByIdentifier.toList(), *matches) + } + + + private fun assertDeclares(text: String, vararg declarations: JvmDeclared) { + val file = myFixture.addFileToProject("Declaraions.kt", text.trimIndent()) as KtFile + assertMatches(collectJvmDeclarations(file).entries, *declarations) + } + + private fun collectJvmDeclarations(file: KtFile): MutableMap> { + val declarationSearcher = JvmDeclarationSearcher.EP.forLanguage(KotlinLanguage.INSTANCE)!! + + val map = mutableMapOf>() + + file.accept(object : PsiRecursiveElementVisitor() { + + override fun visitElement(element: PsiElement) { + val declarations = declarationSearcher.findDeclarations(element) + if (declarations.isNotEmpty()) { + map[element] = declarations.toList() + } + super.visitElement(element) + } + }) + return map + } + +} + +private class JvmDeclared(val textToContain: String, vararg jvmClasses: KClass) : + Function1>, Boolean> { + private val jvmClasses = jvmClasses.toList() + + override fun invoke(p1: Map.Entry>): Boolean { + val (psi, jvmElements) = p1 + if (!psi.text.contains(textToContain)) return false + + return matchElementsToConditions(jvmElements, jvmClasses.map { { value: JvmElement -> it.isInstance(value) } }).succeed + } + + override fun toString(): String = "JvmDeclaration contains text '$textToContain' and produces $jvmClasses" +} + +fun assertMatches(elements: Collection, vararg conditions: (T) -> Boolean) { + val matchResult = matchElementsToConditions(elements, conditions.toList()) + when (matchResult) { + is MatchResult.UnmatchedCondition -> + throw AssertionError("no one matches the ${matchResult.condition}, elements = ${elements.joinToString { it.toString() }}") + is MatchResult.UnmatchedElements -> + throw AssertionError("elements ${matchResult.elements.joinToString { it.toString() }} wasn't matched by any condition") + } +} + +private fun matchElementsToConditions(elements: Collection, conditions: List<(T) -> Boolean>): MatchResult { + val checkList = conditions.toMutableList() + val elementsToCheck = elements.toMutableList() + + while (checkList.isNotEmpty()) { + val condition = checkList.removeAt(0) + val matched = elementsToCheck.find { condition(it) } + ?: return MatchResult.UnmatchedCondition(condition) + if (!elementsToCheck.remove(matched)) + throw IllegalStateException("cant remove matched element: $matched") + } + if (elementsToCheck.isEmpty()) + return MatchResult.Matched + return MatchResult.UnmatchedElements(elementsToCheck) +} + +private sealed class MatchResult(val succeed: Boolean) { + object Matched : MatchResult(true) + class UnmatchedCondition(val condition: (T) -> Boolean) : MatchResult(false) + class UnmatchedElements(val elements: List) : MatchResult(false) +} \ No newline at end of file