diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/api/KDoc.java b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/api/KDoc.java index bd1a86f5384..b18c57c2a06 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/api/KDoc.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/api/KDoc.java @@ -18,8 +18,10 @@ package org.jetbrains.kotlin.kdoc.psi.api; import com.intellij.psi.PsiComment; import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection; +import org.jetbrains.kotlin.psi.JetDeclaration; // Don't implement JetElement (or it will be treated as statement) public interface KDoc extends PsiComment { + JetDeclaration getOwner(); KDocSection getDefaultSection(); } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocImpl.java b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocImpl.java deleted file mode 100644 index 4db5d18e0c3..00000000000 --- a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocImpl.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.kdoc.psi.impl; - -import com.intellij.lang.Language; -import com.intellij.psi.impl.source.tree.LazyParseablePsiElement; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.idea.JetLanguage; -import org.jetbrains.kotlin.kdoc.lexer.KDocTokens; -import org.jetbrains.kotlin.kdoc.psi.api.KDoc; -import org.jetbrains.kotlin.lexer.JetTokens; - -public class KDocImpl extends LazyParseablePsiElement implements KDoc { - public KDocImpl(CharSequence buffer) { - super(KDocTokens.KDOC, buffer); - } - - @NotNull - @Override - public Language getLanguage() { - return JetLanguage.INSTANCE; - } - - @Override - public String toString() { - return getNode().getElementType().toString(); - } - - @Override - public IElementType getTokenType() { - return JetTokens.DOC_COMMENT; - } - - @Override - public KDocSection getDefaultSection() { - return PsiTreeUtil.getChildOfType(this, KDocSection.class); - } -} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocImpl.kt b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocImpl.kt new file mode 100644 index 00000000000..1d55fe3be3f --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocImpl.kt @@ -0,0 +1,41 @@ +/* + * 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.kdoc.psi.impl + +import com.intellij.lang.Language +import com.intellij.psi.impl.source.tree.LazyParseablePsiElement +import com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.idea.JetLanguage +import org.jetbrains.kotlin.kdoc.lexer.KDocTokens +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.psi.JetDeclaration +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType + +public class KDocImpl(buffer: CharSequence?) : LazyParseablePsiElement(KDocTokens.KDOC, buffer), KDoc { + + override fun getLanguage(): Language = JetLanguage.INSTANCE + + override fun toString(): String = getNode().getElementType().toString() + + override fun getTokenType(): IElementType = JetTokens.DOC_COMMENT + + override fun getOwner(): JetDeclaration = getParentOfType(true)!! + + override fun getDefaultSection(): KDocSection = getChildOfType()!! +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocLink.kt b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocLink.kt index 620cd0cc76a..27dc011ca4d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocLink.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocLink.kt @@ -17,13 +17,32 @@ package org.jetbrains.kotlin.kdoc.psi.impl import com.intellij.lang.ASTNode +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiReference +import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry +import org.jetbrains.kotlin.psi.JetElementImpl +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType -public class KDocLink(node: ASTNode) : KDocElementImpl(node) { - fun getLinkText(): String { +public class KDocLink(node: ASTNode) : JetElementImpl(node) { + public fun getLinkText(): String = getLinkTextRange().substring(getText()) + + public fun getLinkTextRange(): TextRange { val text = getText() if (text.startsWith('[') && text.endsWith(']')) { - return text.substring(1, text.length() - 1) + return TextRange(1, text.length()-1) } - return text + return TextRange(0, text.length()) } + + /** + * If this link is the subject of a tag, returns the tag. Otherwise, returns null. + */ + public fun getTagIfSubject(): KDocTag? { + val tag = getStrictParentOfType() + return if (tag != null && tag.getSubjectLink() == this) tag else null + } + + override fun getReferences(): Array? = + ReferenceProvidersRegistry.getReferencesFromProviders(this) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocName.kt b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocName.kt index 61391115ee0..7493c54b8a9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocName.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocName.kt @@ -18,10 +18,38 @@ package org.jetbrains.kotlin.kdoc.psi.impl import com.intellij.lang.ASTNode import org.jetbrains.kotlin.psi.JetElementImpl +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType /** * A single part of a qualified name in the tag subject or link. */ public class KDocName(node: ASTNode): JetElementImpl(node) { + public fun getContainingDoc(): KDoc { + val kdoc = getStrictParentOfType() + if (kdoc == null) { + throw IllegalStateException("KDocName must be inside a KDoc") + } + return kdoc + } + public fun getQualifier(): KDocName? = getChildOfType() + + public fun getNameTextRange(): TextRange { + val dot = getNode().findChildByType(JetTokens.DOT) + val textRange = getTextRange() + val nameStart = if (dot != null) dot.getTextRange().getEndOffset() - textRange.getStartOffset() else 0 + return TextRange(nameStart, textRange.getLength()) + } + + public fun getNameText(): String = getNameTextRange().substring(getText()) + + public fun getQualifiedName(): List { + val qualifier = getQualifier() + val nameAsList = listOf(getNameText()) + return if (qualifier != null) qualifier.getQualifiedName() + nameAsList else nameAsList + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocSection.kt b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocSection.kt index 52b8fc77af9..c7e52775df3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocSection.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocSection.kt @@ -17,7 +17,7 @@ package org.jetbrains.kotlin.kdoc.psi.impl import com.intellij.lang.ASTNode -import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType /** * The part of a doc comment which describes a single class, method or property @@ -27,11 +27,7 @@ import com.intellij.psi.util.PsiTreeUtil */ public class KDocSection(node: ASTNode) : KDocTag(node) { public fun findTagsByName(name: String): List { - val tags = PsiTreeUtil.getChildrenOfType(this, javaClass()) - if (tags == null) { - return listOf() - } - return tags.filter { it.getName() == name } + return getChildrenOfType().filter { it.getName() == name } } public fun findTagByName(name: String): KDocTag? diff --git a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocTag.kt b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocTag.kt index 9e8ae9d370f..996b9b4a119 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocTag.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/kdoc/psi/impl/KDocTag.kt @@ -44,10 +44,12 @@ public open class KDocTag(node: ASTNode) : KDocElementImpl(node) { * Returns the name of the entity documented by this tag (for example, the name of the parameter * for the @param tag), or null if this tag does not document any specific entity. */ - public fun getSubjectName(): String? { + public fun getSubjectName(): String? = getSubjectLink()?.getLinkText() + + public fun getSubjectLink(): KDocLink? { val children = childrenAfterTagName() if (hasSubject(children)) { - return (children.firstOrNull()?.getPsi() as? KDocLink)?.getLinkText() + return children.firstOrNull()?.getPsi() as? KDocLink } return null } @@ -66,6 +68,10 @@ public open class KDocTag(node: ASTNode) : KDocElementImpl(node) { .dropWhile { it.getElementType() == KDocTokens.TAG_NAME } .dropWhile { it.getElementType() == TokenType.WHITE_SPACE } + /** + * Returns the contents of this tag (all text following the tag name and the subject if present, + * with leading asterisks removed). + */ public fun getContent(): String { val builder = StringBuilder() diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt index 47a69b15fea..c19f8860d3a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt @@ -87,6 +87,14 @@ inline public fun PsiElement.getNonStrictParentOfType(): return PsiTreeUtil.getParentOfType(this, javaClass(), false) } +inline public fun PsiElement.getChildOfType(): T? { + return PsiTreeUtil.getChildOfType(this, javaClass()) +} + +inline public fun PsiElement.getChildrenOfType(): Array { + return PsiTreeUtil.getChildrenOfType(this, javaClass()) ?: array() +} + public fun PsiElement?.isAncestor(element: PsiElement, strict: Boolean = false): Boolean { return PsiTreeUtil.isAncestor(this, element, strict) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/FunctionDescriptorUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/FunctionDescriptorUtil.java index 96549fef823..bffba11979b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/FunctionDescriptorUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/FunctionDescriptorUtil.java @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl; import org.jetbrains.kotlin.descriptors.impl.TypeParameterDescriptorImpl; import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl; import org.jetbrains.kotlin.resolve.scopes.JetScope; +import org.jetbrains.kotlin.resolve.scopes.RedeclarationHandler; import org.jetbrains.kotlin.resolve.scopes.WritableScope; import org.jetbrains.kotlin.resolve.scopes.WritableScopeImpl; import org.jetbrains.kotlin.types.*; @@ -69,7 +70,17 @@ public class FunctionDescriptorUtil { @NotNull public static JetScope getFunctionInnerScope(@NotNull JetScope outerScope, @NotNull FunctionDescriptor descriptor, @NotNull BindingTrace trace) { - WritableScope parameterScope = new WritableScopeImpl(outerScope, descriptor, new TraceBasedRedeclarationHandler(trace), "Function inner scope"); + TraceBasedRedeclarationHandler redeclarationHandler = new TraceBasedRedeclarationHandler(trace); + return getFunctionInnerScope(outerScope, descriptor, redeclarationHandler); + } + + @NotNull + public static JetScope getFunctionInnerScope( + @NotNull JetScope outerScope, + @NotNull FunctionDescriptor descriptor, + RedeclarationHandler redeclarationHandler + ) { + WritableScope parameterScope = new WritableScopeImpl(outerScope, descriptor, redeclarationHandler, "Function inner scope"); ReceiverParameterDescriptor receiver = descriptor.getExtensionReceiverParameter(); if (receiver != null) { parameterScope.setImplicitReceiver(receiver); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java index 01d27af523e..a44dbfc96cf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/scopes/JetScopeUtils.java @@ -67,6 +67,19 @@ public final class JetScopeUtils { return accessorScope; } + public static JetScope getPropertyDeclarationInnerScope( + @NotNull PropertyDescriptor propertyDescriptor, + @NotNull JetScope outerScope, + RedeclarationHandler redeclarationHandler + ) { + return getPropertyDeclarationInnerScope(propertyDescriptor, + outerScope, + propertyDescriptor.getTypeParameters(), + propertyDescriptor.getExtensionReceiverParameter(), + redeclarationHandler, + true); + } + public static JetScope getPropertyDeclarationInnerScope( @NotNull PropertyDescriptor propertyDescriptor, @NotNull JetScope outerScope, @@ -94,9 +107,23 @@ public final class JetScopeUtils { @Nullable ReceiverParameterDescriptor receiver, BindingTrace trace, boolean addLabelForProperty + ) { + TraceBasedRedeclarationHandler redeclarationHandler = new TraceBasedRedeclarationHandler(trace); + return getPropertyDeclarationInnerScope(propertyDescriptor, outerScope, typeParameters, receiver, redeclarationHandler, + addLabelForProperty); + } + + @NotNull + private static JetScope getPropertyDeclarationInnerScope( + @Nullable PropertyDescriptor propertyDescriptor, + @NotNull JetScope outerScope, + @NotNull List typeParameters, + @Nullable ReceiverParameterDescriptor receiver, + RedeclarationHandler redeclarationHandler, + boolean addLabelForProperty ) { WritableScopeImpl result = new WritableScopeImpl( - outerScope, outerScope.getContainingDeclaration(), new TraceBasedRedeclarationHandler(trace), + outerScope, outerScope.getContainingDeclaration(), redeclarationHandler, "Property declaration inner scope"); if (addLabelForProperty) { assert propertyDescriptor != null : "PropertyDescriptor can be null for property scope which hasn't label to property"; diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 0176b5ba0c2..cab72cc3bc4 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -669,6 +669,10 @@ fun main(args: Array) { testClass(javaClass()) { model("coverage/outputFiles") } + + testClass(javaClass(), "org.jetbrains.kotlin.idea.kdoc.KdocResolveTestGenerated") { + model("kdoc/resolve") + } } testGroup("idea/tests", "compiler/testData") { @@ -733,10 +737,14 @@ private class TestGroup(val testsRoot: String, val testDataRoot: String) { val testClass = TestClass() testClass.init() + val lastDot = suiteTestClass.lastIndexOf('.') + val suiteTestClassName = if (lastDot == -1) suiteTestClass else suiteTestClass.substring(lastDot+1) + val suiteTestClassPackage = if (lastDot == -1) baseTestClass.getPackage().getName() else suiteTestClass.substring(0, lastDot) + TestGenerator( testsRoot, - baseTestClass.getPackage()!!.getName()!!, - suiteTestClass, + suiteTestClassPackage, + suiteTestClassName, baseTestClass, testClass.testModels ).generateAndSave() diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/generator/TestGenerator.java b/generators/src/org/jetbrains/kotlin/generators/tests/generator/TestGenerator.java index 53535a20907..3153324650a 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/generator/TestGenerator.java +++ b/generators/src/org/jetbrains/kotlin/generators/tests/generator/TestGenerator.java @@ -48,6 +48,7 @@ public class TestGenerator { private static final Set GENERATED_FILES = ContainerUtil.newHashSet(); private static final Class RUNNER = JUnit3RunnerWithInners.class; + private final String baseTestClassPackage; private final String suiteClassPackage; private final String suiteClassName; private final String baseTestClassName; @@ -63,6 +64,7 @@ public class TestGenerator { ) { this.suiteClassPackage = suiteClassPackage; this.suiteClassName = suiteClassName; + this.baseTestClassPackage = baseTestClass.getPackage().getName(); this.baseTestClassName = baseTestClass.getSimpleName(); this.testClassModels = Lists.newArrayList(testClassModels); @@ -84,6 +86,9 @@ public class TestGenerator { p.println("import " + InnerTestClasses.class.getCanonicalName() + ";"); p.println("import ", RUNNER.getCanonicalName(), ";"); p.println("import " + JetTestUtils.class.getCanonicalName() + ";"); + if (!suiteClassPackage.equals(baseTestClassPackage)) { + p.println("import " + baseTestClassPackage + "." + baseTestClassName + ";"); + } p.println("import " + TestMetadata.class.getCanonicalName() + ";"); p.println("import " + RunWith.class.getCanonicalName() + ";"); p.println(); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocElementFactory.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocElementFactory.kt new file mode 100644 index 00000000000..e82c801f970 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocElementFactory.kt @@ -0,0 +1,41 @@ +/* + * 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.kdoc + +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.psi.JetPsiFactory +import org.jetbrains.kotlin.psi.JetFunction +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.kdoc.psi.impl.KDocName +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType + +class KDocElementFactory(val project: Project) { + public fun createKDocFromText(text: String): KDoc { + val fileText = text + " fun foo { }" + val function = JetPsiFactory(project).createDeclaration(fileText) + return PsiTreeUtil.findChildOfType(function, javaClass())!! + } + + public fun createNameFromText(text: String): KDocName { + val kdoc = createKDocFromText("/** @param $text foo*/") + val section = kdoc.getDefaultSection() + val tag = section.findTagByName("param") + val link = tag!!.getSubjectLink()!! + return link.getChildOfType()!! + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt new file mode 100644 index 00000000000..c71bb560277 --- /dev/null +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt @@ -0,0 +1,172 @@ +/* + * 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.kdoc + +import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink +import com.intellij.openapi.util.TextRange +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.references.JetMultiReference +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorNonRoot +import org.jetbrains.kotlin.resolve.scopes.JetScope +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.descriptors.PackageViewDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.resolve.scopes.RedeclarationHandler +import org.jetbrains.kotlin.resolve.scopes.WritableScopeImpl +import org.jetbrains.kotlin.resolve.scopes.WritableScope +import org.jetbrains.kotlin.resolve.scopes.ChainedScope +import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil +import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils +import org.jetbrains.kotlin.kdoc.psi.impl.KDocName +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.descriptors.ClassifierDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer +import org.jetbrains.kotlin.resolve.source.PsiSourceElement +import org.jetbrains.kotlin.psi.JetFile +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource + +public class KDocReference(element: KDocName): JetMultiReference(element) { + override fun getTargetDescriptors(context: BindingContext): Collection { + val cacheService = KotlinCacheService.getInstance(getElement().getProject()) + val session = cacheService.getLazyResolveSession(getElement()) + val declaration = getElement().getContainingDoc().getOwner() + if (declaration == null) { + return arrayListOf() + } + val declarationDescriptor = context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) + val kdocLink = getElement().getStrictParentOfType()!! + return resolveKDocLink(session, declarationDescriptor, kdocLink.getTagIfSubject(), getElement().getQualifiedName()) + } + + override fun getRangeInElement(): TextRange = getElement().getNameTextRange() + + override fun canRename(): Boolean = true + + override fun handleElementRename(newElementName: String?): PsiElement? { + val textRange = getElement().getNameTextRange() + val newText = textRange.replace(getElement().getText(), newElementName) + val newLink = KDocElementFactory(getElement().getProject()).createNameFromText(newText) + return getElement().replace(newLink) + } +} + +public fun resolveKDocLink(session: KotlinCodeAnalyzer, + fromDescriptor: DeclarationDescriptor, + fromSubjectOfTag: KDocTag?, + qualifiedName: List): Collection { + if (fromSubjectOfTag?.getName() == "param") { + return resolveParamLink(fromDescriptor, qualifiedName) + } + + var result: Collection = listOf(fromDescriptor) + qualifiedName.forEach { nameComponent -> + if (result.size() != 1) return listOf() + val scope = getResolutionScope(session, result.first()) + result = scope.getDescriptors().filter { it.getName().asString() == nameComponent } + } + + return result +} + +private fun resolveParamLink(fromDescriptor: DeclarationDescriptor, qualifiedName: List): List { + if (qualifiedName.size() != 1) { + return listOf() + } + if (fromDescriptor is CallableDescriptor) { + val valueParams = fromDescriptor.getValueParameters().filter { it.getName().asString() == qualifiedName[0] } + if (valueParams.isNotEmpty()) { + return valueParams + } + } + if (fromDescriptor is ClassifierDescriptor) { + val typeParams = fromDescriptor.getTypeConstructor().getParameters().filter { it.getName().asString() == qualifiedName[0] } + if (typeParams.isNotEmpty()) { + return typeParams + } + } + + return listOf() +} + +private fun getPackageInnerScope(descriptor: PackageFragmentDescriptor): JetScope { + val module = descriptor.getContainingDeclaration() + val packageView = module.getPackage(descriptor.fqName) + val packageScope = packageView!!.getMemberScope() + return packageScope +} + +private fun getClassInnerScope(outerScope: JetScope, descriptor: ClassDescriptor): JetScope { + val redeclarationHandler = RedeclarationHandler.DO_NOTHING + + val headerScope = WritableScopeImpl(outerScope, descriptor, redeclarationHandler, "Class ${descriptor.getName()} header scope") + for (typeParameter in descriptor.getTypeConstructor().getParameters()) { + headerScope.addTypeParameterDescriptor(typeParameter) + } + for (constructor in descriptor.getConstructors()) { + headerScope.addFunctionDescriptor(constructor) + } + headerScope.addLabeledDeclaration(descriptor) + headerScope.changeLockLevel(WritableScope.LockLevel.READING) + + val classScope = ChainedScope(descriptor, "Class ${descriptor.getName()} scope", descriptor.getDefaultType().getMemberScope(), headerScope) + return classScope +} + +private fun getResolutionScope(session: KotlinCodeAnalyzer, descriptor: DeclarationDescriptor): JetScope { + when (descriptor) { + is PackageFragmentDescriptor -> + return getPackageInnerScope(descriptor) + + is PackageViewDescriptor -> + return descriptor.getMemberScope() + + is ClassDescriptor -> + return getClassInnerScope(getOuterScope(descriptor, session), descriptor) + + is FunctionDescriptor -> + return FunctionDescriptorUtil.getFunctionInnerScope(getOuterScope(descriptor, session), + descriptor, RedeclarationHandler.DO_NOTHING) + + is PropertyDescriptor -> + return JetScopeUtils.getPropertyDeclarationInnerScope(descriptor, + getOuterScope(descriptor, session), + RedeclarationHandler.DO_NOTHING) + } + + if (descriptor is DeclarationDescriptorNonRoot) + return getOuterScope(descriptor, session) + + throw IllegalArgumentException("Cannot find resolution scope for root $descriptor") +} + +private fun getOuterScope(descriptor: DeclarationDescriptorWithSource, session: KotlinCodeAnalyzer): JetScope { + val parent = descriptor.getContainingDeclaration() + if (parent is PackageFragmentDescriptor) { + val containingFile = (descriptor.getSource() as? PsiSourceElement)?.psi?.getContainingFile() as? JetFile + if (containingFile != null) { + return session.getScopeProvider().getFileScope(containingFile) + } + } + return getResolutionScope(session, parent) +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt index 22ebb7dde04..7e2d5fb98e8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/JetReferenceContributor.kt @@ -20,6 +20,8 @@ import com.intellij.psi.* import org.jetbrains.kotlin.psi.* import com.intellij.util.ProcessingContext import com.intellij.patterns.PlatformPatterns +import org.jetbrains.kotlin.idea.kdoc.KDocReference +import org.jetbrains.kotlin.kdoc.psi.impl.KDocName public class JetReferenceContributor() : PsiReferenceContributor() { public override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { @@ -45,6 +47,9 @@ public class JetReferenceContributor() : PsiReferenceContributor() { registerProvider(javaClass()) { JetMultiDeclarationReference(it) } + registerProvider(javaClass()) { + KDocReference(it) + } } } diff --git a/idea/testData/kdoc/rename/CodeReference.kt b/idea/testData/kdoc/rename/CodeReference.kt new file mode 100644 index 00000000000..09004500c59 --- /dev/null +++ b/idea/testData/kdoc/rename/CodeReference.kt @@ -0,0 +1,6 @@ +/** + * This is the same as [bar] + */ +fun foo() {} + +fun bar() {} diff --git a/idea/testData/kdoc/rename/CodeReference.kt.after b/idea/testData/kdoc/rename/CodeReference.kt.after new file mode 100644 index 00000000000..f8e945f2489 --- /dev/null +++ b/idea/testData/kdoc/rename/CodeReference.kt.after @@ -0,0 +1,6 @@ +/** + * This is the same as [xyzzy] + */ +fun foo() {} + +fun xyzzy() {} diff --git a/idea/testData/kdoc/rename/ParamReference.kt b/idea/testData/kdoc/rename/ParamReference.kt new file mode 100644 index 00000000000..f7699ca6f2c --- /dev/null +++ b/idea/testData/kdoc/rename/ParamReference.kt @@ -0,0 +1,4 @@ +/** + * @param s this is a string + */ +fun f(s: String) { } diff --git a/idea/testData/kdoc/rename/ParamReference.kt.after b/idea/testData/kdoc/rename/ParamReference.kt.after new file mode 100644 index 00000000000..6a82f593858 --- /dev/null +++ b/idea/testData/kdoc/rename/ParamReference.kt.after @@ -0,0 +1,4 @@ +/** + * @param bar this is a string + */ +fun f(bar: String) { } diff --git a/idea/testData/kdoc/resolve/CodeReference.kt b/idea/testData/kdoc/resolve/CodeReference.kt new file mode 100644 index 00000000000..7b3f8833ee2 --- /dev/null +++ b/idea/testData/kdoc/resolve/CodeReference.kt @@ -0,0 +1,8 @@ +/** + * This is the same as [bar] + */ +fun foo() {} + +fun bar() {} + +// REF: (in ).bar() diff --git a/idea/testData/kdoc/resolve/ImportedClassReference.kt b/idea/testData/kdoc/resolve/ImportedClassReference.kt new file mode 100644 index 00000000000..1bd038cedff --- /dev/null +++ b/idea/testData/kdoc/resolve/ImportedClassReference.kt @@ -0,0 +1,10 @@ +import java.util.Properties + +/** + * This is almost like [Properties] + */ +class C { + +} + +// REF: (java.util).Properties diff --git a/idea/testData/kdoc/resolve/ParamReference.kt b/idea/testData/kdoc/resolve/ParamReference.kt new file mode 100644 index 00000000000..ce68a47b3a8 --- /dev/null +++ b/idea/testData/kdoc/resolve/ParamReference.kt @@ -0,0 +1,6 @@ +/** + * @param s this is a string + */ +fun f(s: String) { } + +// REF: s diff --git a/idea/testData/kdoc/resolve/PropertyTypeParamReference.kt b/idea/testData/kdoc/resolve/PropertyTypeParamReference.kt new file mode 100644 index 00000000000..1cffb420beb --- /dev/null +++ b/idea/testData/kdoc/resolve/PropertyTypeParamReference.kt @@ -0,0 +1,6 @@ +/** + * The type [T] is the type on which the foo property is defined. + */ +val T.foo: Int get() = 0 + +// REF: T diff --git a/idea/testData/kdoc/resolve/QualifiedCodeReference.kt b/idea/testData/kdoc/resolve/QualifiedCodeReference.kt new file mode 100644 index 00000000000..8d494a1b70b --- /dev/null +++ b/idea/testData/kdoc/resolve/QualifiedCodeReference.kt @@ -0,0 +1,10 @@ +/** + * This is the same as [C.bar] + */ +fun foo() {} + +class C { + fun bar() {} +} + +// REF: (in C).bar() diff --git a/idea/testData/kdoc/resolve/TypeParamReference.kt b/idea/testData/kdoc/resolve/TypeParamReference.kt new file mode 100644 index 00000000000..fad4052e8f7 --- /dev/null +++ b/idea/testData/kdoc/resolve/TypeParamReference.kt @@ -0,0 +1,6 @@ +/** + * @param T this is the type + */ +class Foo {} + +// REF: T diff --git a/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocRenameTest.java b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocRenameTest.java new file mode 100644 index 00000000000..844e9242b64 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocRenameTest.java @@ -0,0 +1,50 @@ +/* + * 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.kdoc; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.psi.PsiElement; +import com.intellij.refactoring.rename.RenameProcessor; +import com.intellij.testFramework.LightCodeInsightTestCase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.idea.PluginTestCaseBase; + +public class KdocRenameTest extends LightCodeInsightTestCase { + @NotNull + @Override + protected String getTestDataPath() { + return PluginTestCaseBase.getTestDataPathBase() + "/kdoc/rename/"; + } + + public void testParamReference() throws Exception { + doTest("bar"); + } + + public void testCodeReference() throws Exception { + doTest("xyzzy"); + } + + private void doTest(String newName) throws Exception { + configureByFile(getTestName(false) + ".kt"); + PsiElement element = TargetElementUtilBase + .findTargetElement(myEditor, + TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED); + assertNotNull(element); + new RenameProcessor(getProject(), element, newName, true, true).run(); + checkResultByFile(getTestName(false) + ".kt.after"); + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java new file mode 100644 index 00000000000..984f0f144b3 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java @@ -0,0 +1,74 @@ +/* + * 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.kdoc; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.idea.resolve.AbstractReferenceResolveTest; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.JetTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/testData/kdoc/resolve") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class KdocResolveTestGenerated extends AbstractReferenceResolveTest { + public void testAllFilesPresentInResolve() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/kdoc/resolve"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("CodeReference.kt") + public void testCodeReference() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/resolve/CodeReference.kt"); + doTest(fileName); + } + + @TestMetadata("ImportedClassReference.kt") + public void testImportedClassReference() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/resolve/ImportedClassReference.kt"); + doTest(fileName); + } + + @TestMetadata("ParamReference.kt") + public void testParamReference() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/resolve/ParamReference.kt"); + doTest(fileName); + } + + @TestMetadata("PropertyTypeParamReference.kt") + public void testPropertyTypeParamReference() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/resolve/PropertyTypeParamReference.kt"); + doTest(fileName); + } + + @TestMetadata("QualifiedCodeReference.kt") + public void testQualifiedCodeReference() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/resolve/QualifiedCodeReference.kt"); + doTest(fileName); + } + + @TestMetadata("TypeParamReference.kt") + public void testTypeParamReference() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/resolve/TypeParamReference.kt"); + doTest(fileName); + } +}