diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/PsiChildRange.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/PsiChildRange.kt new file mode 100644 index 00000000000..75578655994 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/PsiChildRange.kt @@ -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.psi.psiUtil + +import com.intellij.psi.PsiElement + +public data class PsiChildRange(public val first: PsiElement?, public val last: PsiElement?) : Sequence { + init { + if (first == null) { + assert(last == null) + } + else { + assert(first.getParent() == last!!.getParent()) + } + } + + public val isEmpty: Boolean + get() = first == null + + override fun iterator(): Iterator { + val sequence = if (first == null) { + emptySequence() + } + else { + val afterLast = last!!.getNextSibling() + first.siblings().takeWhile { it != afterLast } + } + return sequence.iterator() + } + + companion object { + public val EMPTY: PsiChildRange = PsiChildRange(null, null) + + public fun singleElement(element: PsiElement): PsiChildRange = PsiChildRange(element, element) + } +} \ No newline at end of file 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 abad4dcfa37..39dea83a0ad 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt @@ -47,71 +47,6 @@ public fun JetCallElement.getCallNameExpression(): JetSimpleNameExpression? { } } -public fun PsiElement.getParentOfTypesAndPredicate( - strict : Boolean = false, vararg parentClasses : Class, predicate: (T) -> Boolean -) : T? { - var element = if (strict) getParent() else this - while (element != null) { - @suppress("UNCHECKED_CAST") - when { - (parentClasses.isEmpty() || parentClasses.any {parentClass -> parentClass.isInstance(element)}) && predicate(element!! as T) -> - return element as T - element is PsiFile -> - return null - else -> - element = element!!.getParent() - } - } - - return null -} - -public fun PsiElement.getNonStrictParentOfType(parentClass : Class) : T? { - return PsiTreeUtil.getParentOfType(this, parentClass, false) -} - -inline public fun PsiElement.getParentOfType(strict: Boolean): T? { - return PsiTreeUtil.getParentOfType(this, javaClass(), strict) -} - -inline public fun PsiElement.getStrictParentOfType(): T? { - return PsiTreeUtil.getParentOfType(this, javaClass(), true) -} - -inline public fun PsiElement.getNonStrictParentOfType(): T? { - 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()) ?: arrayOf() -} - -public fun PsiElement.getNextSiblingIgnoringWhitespaceAndComments(): PsiElement? { - var current = this - do { - current = current.getNextSibling() - if (current == null) return null - } - while (current is PsiComment || current is PsiWhiteSpace) - return current -} - -public fun PsiElement?.isAncestor(element: PsiElement, strict: Boolean = false): Boolean { - return PsiTreeUtil.isAncestor(this, element, strict) -} - -public fun T.getIfChildIsInBranch(element: PsiElement, branch: T.() -> PsiElement?): T? { - return if (branch().isAncestor(element)) this else null -} - -inline public fun PsiElement.getParentOfTypeAndBranch(strict: Boolean = false, noinline branch: T.() -> PsiElement?) : T? { - return getParentOfType(strict)?.getIfChildIsInBranch(this, branch) -} - public fun JetClassOrObject.effectiveDeclarations(): List = when(this) { is JetClass -> @@ -207,8 +142,6 @@ public fun StubBasedPsiElementBase): Boolean = elements.any { it.isAncestor(this) } - -public tailRecursive fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? { - val parent = getParent() - return if (parent == container) this else parent?.getOutermostParentContainedIn(container) -} - public fun JetSimpleNameExpression.getReceiverExpression(): JetExpression? { val parent = getParent() when { @@ -407,55 +333,11 @@ public fun JetExpression.isFunctionLiteralOutsideParentheses(): Boolean { } } -public fun PsiElement.siblings(forward: Boolean = true, withItself: Boolean = true): Sequence { - val stepFun = if (forward) { e: PsiElement -> e.getNextSibling() } else { e: PsiElement -> e.getPrevSibling() } - val sequence = sequence(this, stepFun) - return if (withItself) sequence else sequence.drop(1) -} - -public fun ASTNode.siblings(forward: Boolean = true, withItself: Boolean = true): Sequence { - val stepFun = if (forward) { node: ASTNode -> node.getTreeNext() } else { e: ASTNode -> e.getTreeNext() } - val sequence = sequence(this, stepFun) - return if (withItself) sequence else sequence.drop(1) -} - -public fun PsiElement.parents(withItself: Boolean = true): Sequence { - val sequence = sequence(this) { if (it is PsiFile) null else it.getParent() } - return if (withItself) sequence else sequence.drop(1) -} - -public fun ASTNode.parents(withItself: Boolean = true): Sequence { - val sequence = sequence(this) { it.getTreeParent() } - return if (withItself) sequence else sequence.drop(1) -} - public fun JetExpression.getAssignmentByLHS(): JetBinaryExpression? { val parent = getParent() as? JetBinaryExpression ?: return null return if (JetPsiUtil.isAssignment(parent) && parent.getLeft() == this) parent else null } -public fun PsiElement.prevLeaf(skipEmptyElements: Boolean = false): PsiElement? - = PsiTreeUtil.prevLeaf(this, skipEmptyElements) - -public fun PsiElement.nextLeaf(skipEmptyElements: Boolean = false): PsiElement? - = PsiTreeUtil.nextLeaf(this, skipEmptyElements) - -public fun PsiElement.prevLeaf(filter: (PsiElement) -> Boolean): PsiElement? { - var leaf = prevLeaf() - while (leaf != null && !filter(leaf)) { - leaf = leaf.prevLeaf() - } - return leaf -} - -public fun PsiElement.nextLeaf(filter: (PsiElement) -> Boolean): PsiElement? { - var leaf = nextLeaf() - while (leaf != null && !filter(leaf)) { - leaf = leaf.nextLeaf() - } - return leaf -} - public fun JetExpression.isDotReceiver(): Boolean = (getParent() as? JetDotQualifiedExpression)?.getReceiverExpression() == this @@ -481,42 +363,11 @@ public fun JetStringTemplateExpression.getContentRange(): TextRange { public fun JetStringTemplateExpression.isSingleQuoted(): Boolean = getNode().getFirstChildNode().getTextLength() == 1 -public fun PsiFile.elementsInRange(range: TextRange): List { - var offset = range.getStartOffset() - val result = ArrayList() - while (offset < range.getEndOffset()) { - val currentRange = TextRange(offset, range.getEndOffset()) - val leaf = findFirstLeafWhollyInRange(this, currentRange) ?: break - - val element = leaf - .parents(withItself = true) - .first { - val parent = it.getParent() - it is PsiFile || parent.getTextRange() !in currentRange - } - result.add(element) - - offset = element.endOffset - } - return result -} - -private fun findFirstLeafWhollyInRange(file: PsiFile, range: TextRange): PsiElement? { - var element = file.findElementAt(range.getStartOffset()) ?: return null - var elementRange = element.getTextRange() - if (elementRange.getStartOffset() < range.getStartOffset()) { - element = element.nextLeaf(skipEmptyElements = true) ?: return null - elementRange = element.getTextRange() - } - assert(elementRange.getStartOffset() >= range.getStartOffset()) - return if (elementRange.getEndOffset() <= range.getEndOffset()) element else null -} - -fun JetNamedDeclaration.getValueParameters(): List { +public fun JetNamedDeclaration.getValueParameters(): List { return getValueParameterList()?.getParameters() ?: Collections.emptyList() } -fun JetNamedDeclaration.getValueParameterList(): JetParameterList? { +public fun JetNamedDeclaration.getValueParameterList(): JetParameterList? { return when (this) { is JetCallableDeclaration -> getValueParameterList() is JetClass -> getPrimaryConstructorParameterList() @@ -524,26 +375,6 @@ fun JetNamedDeclaration.getValueParameterList(): JetParameterList? { } } -public fun PsiElement.getElementTextWithContext(): String { - if (this is PsiFile) { - return getContainingFile().getText() - } - - // Find parent for element among file children - val topLevelElement = PsiTreeUtil.findFirstParent(this, { it.getParent() is PsiFile }) ?: - throw AssertionError("For non-file element we should always be able to find parent in file children") - - val startContextOffset = topLevelElement.startOffset - val elementContextOffset = getTextRange().getStartOffset() - - val inFileParentOffset = elementContextOffset - startContextOffset - - return StringBuilder(topLevelElement.getText()) - .insert(inFileParentOffset, "") - .insert(0, "File name: ${getContainingFile().getName()}\n") - .toString() -} - // Calls `block` on each descendant of T type // Note, that calls happen in order of DFS-exit, so deeper nodes are applied earlier public inline fun forEachDescendantOfTypeVisitor(noinline block: (T) -> Unit): JetVisitorVoid { @@ -561,46 +392,6 @@ public inline fun flatMapDescendantsOfTypeVisitor(ac return forEachDescendantOfTypeVisitor { accumulator.addAll(map(it)) } } -public inline fun PsiElement.forEachDescendantOfType(noinline action: (T) -> Unit) { - this.accept(object : PsiRecursiveElementVisitor(){ - override fun visitElement(element: PsiElement) { - super.visitElement(element) - if (element is T) { - action(element) - } - } - }) -} - -public inline fun PsiElement.anyDescendantOfType(noinline predicate: (T) -> Boolean = { true }): Boolean { - return findDescendantOfType(predicate) != null -} - -public inline fun PsiElement.findDescendantOfType(noinline predicate: (T) -> Boolean = { true }): T? { - var result: T? = null - this.accept(object : PsiRecursiveElementVisitor(){ - override fun visitElement(element: PsiElement) { - if (result != null) return - if (element is T && predicate(element)) { - result = element - return - } - super.visitElement(element) - } - }) - return result -} - -public inline fun PsiElement.collectDescendantsOfType(noinline predicate: (T) -> Boolean = { true }): List { - val result = ArrayList() - forEachDescendantOfType { - if (predicate(it)) { - result.add(it) - } - } - return result -} - public fun PsiFile.getFqNameByDirectory(): FqName { val qualifiedNameByDirectory = getParent()?.getPackage()?.getQualifiedName() return qualifiedNameByDirectory?.let { FqName(it) } ?: FqName.ROOT diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt index f1523a61cb3..ecf04c20c90 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/psiUtils.kt @@ -16,10 +16,182 @@ package org.jetbrains.kotlin.psi.psiUtil +import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement +import com.intellij.psi.* +import com.intellij.psi.search.PsiSearchScopeUtil +import com.intellij.psi.search.SearchScope +import com.intellij.psi.util.PsiTreeUtil +import java.util.ArrayList -//TODO: move here more functions from jetPsiUtil.kt +// NOTE: in this file we collect only LANGUAGE INDEPENDENT methods working with PSI and not modifying it + +// ----------- Walking children/siblings/parents ------------------------------------------------------------------------------------------- + +public val PsiElement.allChildren: PsiChildRange + get() { + val first = getFirstChild() + return if (first != null) PsiChildRange(first, getLastChild()) else PsiChildRange.EMPTY + } +public fun PsiElement.siblings(forward: Boolean = true, withItself: Boolean = true): Sequence { + val stepFun = if (forward) { e: PsiElement -> e.getNextSibling() } else { e: PsiElement -> e.getPrevSibling() } + val sequence = sequence(this, stepFun) + return if (withItself) sequence else sequence.drop(1) +} + +public fun ASTNode.siblings(forward: Boolean = true, withItself: Boolean = true): Sequence { + val stepFun = if (forward) { node: ASTNode -> node.getTreeNext() } else { e: ASTNode -> e.getTreeNext() } + val sequence = sequence(this, stepFun) + return if (withItself) sequence else sequence.drop(1) +} + +public fun PsiElement.parents(withItself: Boolean = true): Sequence { + val sequence = sequence(this) { if (it is PsiFile) null else it.getParent() } + return if (withItself) sequence else sequence.drop(1) +} + +public fun ASTNode.parents(withItself: Boolean = true): Sequence { + val sequence = sequence(this) { it.getTreeParent() } + return if (withItself) sequence else sequence.drop(1) +} + +public fun PsiElement.prevLeaf(skipEmptyElements: Boolean = false): PsiElement? + = PsiTreeUtil.prevLeaf(this, skipEmptyElements) + +public fun PsiElement.nextLeaf(skipEmptyElements: Boolean = false): PsiElement? + = PsiTreeUtil.nextLeaf(this, skipEmptyElements) + +public fun PsiElement.prevLeaf(filter: (PsiElement) -> Boolean): PsiElement? { + var leaf = prevLeaf() + while (leaf != null && !filter(leaf)) { + leaf = leaf.prevLeaf() + } + return leaf +} + +public fun PsiElement.nextLeaf(filter: (PsiElement) -> Boolean): PsiElement? { + var leaf = nextLeaf() + while (leaf != null && !filter(leaf)) { + leaf = leaf.nextLeaf() + } + return leaf +} + +public fun PsiElement.getParentOfTypesAndPredicate( + strict : Boolean = false, vararg parentClasses : Class, predicate: (T) -> Boolean +) : T? { + var element = if (strict) getParent() else this + while (element != null) { + @suppress("UNCHECKED_CAST") + when { + (parentClasses.isEmpty() || parentClasses.any {parentClass -> parentClass.isInstance(element)}) && predicate(element!! as T) -> + return element as T + element is PsiFile -> + return null + else -> + element = element!!.getParent() + } + } + + return null +} + +public fun PsiElement.getNonStrictParentOfType(parentClass : Class) : T? { + return PsiTreeUtil.getParentOfType(this, parentClass, false) +} + +inline public fun PsiElement.getParentOfType(strict: Boolean): T? { + return PsiTreeUtil.getParentOfType(this, javaClass(), strict) +} + +inline public fun PsiElement.getStrictParentOfType(): T? { + return PsiTreeUtil.getParentOfType(this, javaClass(), true) +} + +inline public fun PsiElement.getNonStrictParentOfType(): T? { + 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()) ?: arrayOf() +} + +public fun PsiElement.getNextSiblingIgnoringWhitespaceAndComments(): PsiElement? { + var current = this + do { + current = current.getNextSibling() + if (current == null) return null + } + while (current is PsiComment || current is PsiWhiteSpace) + return current +} + +public fun PsiElement?.isAncestor(element: PsiElement, strict: Boolean = false): Boolean { + return PsiTreeUtil.isAncestor(this, element, strict) +} + +public fun T.getIfChildIsInBranch(element: PsiElement, branch: T.() -> PsiElement?): T? { + return if (branch().isAncestor(element)) this else null +} + +public inline fun PsiElement.getParentOfTypeAndBranch(strict: Boolean = false, noinline branch: T.() -> PsiElement?) : T? { + return getParentOfType(strict)?.getIfChildIsInBranch(this, branch) +} + +public tailRecursive fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? { + val parent = getParent() + return if (parent == container) this else parent?.getOutermostParentContainedIn(container) +} + +public fun PsiElement.isInsideOf(elements: Iterable): Boolean = elements.any { it.isAncestor(this) } + +// -------------------- Recursive tree visiting -------------------------------------------------------------------------------------------- + +public inline fun PsiElement.forEachDescendantOfType(noinline action: (T) -> Unit) { + this.accept(object : PsiRecursiveElementVisitor(){ + override fun visitElement(element: PsiElement) { + super.visitElement(element) + if (element is T) { + action(element) + } + } + }) +} + +public inline fun PsiElement.anyDescendantOfType(noinline predicate: (T) -> Boolean = { true }): Boolean { + return findDescendantOfType(predicate) != null +} + +public inline fun PsiElement.findDescendantOfType(noinline predicate: (T) -> Boolean = { true }): T? { + var result: T? = null + this.accept(object : PsiRecursiveElementVisitor(){ + override fun visitElement(element: PsiElement) { + if (result != null) return + if (element is T && predicate(element)) { + result = element + return + } + super.visitElement(element) + } + }) + return result +} + +public inline fun PsiElement.collectDescendantsOfType(noinline predicate: (T) -> Boolean = { true }): List { + val result = ArrayList() + forEachDescendantOfType { + if (predicate(it)) { + result.add(it) + } + } + return result +} + +// ----------- Working with offsets, ranges and texts ---------------------------------------------------------------------------------------------- public val PsiElement.startOffset: Int get() = getTextRange().getStartOffset() @@ -37,43 +209,6 @@ public fun PsiElement.getStartOffsetIn(ancestor: PsiElement): Int { return offset } -public data class PsiChildRange(public val first: PsiElement?, public val last: PsiElement?) : Sequence { - init { - if (first == null) { - assert(last == null) - } - else { - assert(first.getParent() == last!!.getParent()) - } - } - - public val isEmpty: Boolean - get() = first == null - - override fun iterator(): Iterator { - val sequence = if (first == null) { - emptySequence() - } - else { - val afterLast = last!!.getNextSibling() - first.siblings().takeWhile { it != afterLast } - } - return sequence.iterator() - } - - companion object { - public val EMPTY: PsiChildRange = PsiChildRange(null, null) - - public fun singleElement(element: PsiElement): PsiChildRange = PsiChildRange(element, element) - } -} - -public val PsiElement.allChildren: PsiChildRange - get() { - val first = getFirstChild() - return if (first != null) PsiChildRange(first, getLastChild()) else PsiChildRange.EMPTY - } - public val PsiChildRange.textRange: TextRange? get() { if (isEmpty) return null @@ -85,3 +220,60 @@ public fun PsiChildRange.getText(): String { return this.map { it.getText() }.joinToString("") } +public fun PsiFile.elementsInRange(range: TextRange): List { + var offset = range.getStartOffset() + val result = ArrayList() + while (offset < range.getEndOffset()) { + val currentRange = TextRange(offset, range.getEndOffset()) + val leaf = findFirstLeafWhollyInRange(this, currentRange) ?: break + + val element = leaf + .parents(withItself = true) + .first { + val parent = it.getParent() + it is PsiFile || parent.getTextRange() !in currentRange + } + result.add(element) + + offset = element.endOffset + } + return result +} + +private fun findFirstLeafWhollyInRange(file: PsiFile, range: TextRange): PsiElement? { + var element = file.findElementAt(range.getStartOffset()) ?: return null + var elementRange = element.getTextRange() + if (elementRange.getStartOffset() < range.getStartOffset()) { + element = element.nextLeaf(skipEmptyElements = true) ?: return null + elementRange = element.getTextRange() + } + assert(elementRange.getStartOffset() >= range.getStartOffset()) + return if (elementRange.getEndOffset() <= range.getEndOffset()) element else null +} + +// ---------------------------------- Debug/logging ---------------------------------------------------------------------------------------- + +public fun PsiElement.getElementTextWithContext(): String { + if (this is PsiFile) { + return getContainingFile().getText() + } + + // Find parent for element among file children + val topLevelElement = PsiTreeUtil.findFirstParent(this, { it.getParent() is PsiFile }) ?: + throw AssertionError("For non-file element we should always be able to find parent in file children") + + val startContextOffset = topLevelElement.startOffset + val elementContextOffset = getTextRange().getStartOffset() + + val inFileParentOffset = elementContextOffset - startContextOffset + + return StringBuilder(topLevelElement.getText()) + .insert(inFileParentOffset, "") + .insert(0, "File name: ${getContainingFile().getName()}\n") + .toString() +} + +// ----------------------------------------------------------------------------------------------------------------------------------------- + +public fun SearchScope.contains(element: PsiElement): Boolean = PsiSearchScopeUtil.isInScope(this, element) +