Optimization to reuse BindingContext in completion
This commit is contained in:
@@ -1146,6 +1146,10 @@ fun main(args: Array<String>) {
|
|||||||
testClass<AbstractJava8BasicCompletionTest> {
|
testClass<AbstractJava8BasicCompletionTest> {
|
||||||
model("basic/java8")
|
model("basic/java8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass<AbstractCompletionIncrementalResolveTest>() {
|
||||||
|
model("incrementalResolve")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: move these tests into idea-completion module
|
//TODO: move these tests into idea-completion module
|
||||||
|
|||||||
+14
-14
@@ -28,6 +28,7 @@ import com.intellij.psi.PsiElement
|
|||||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||||
import com.intellij.psi.util.PsiModificationTracker
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ class KotlinCodeBlockModificationListener(
|
|||||||
override fun modelChanged(event: PomModelEvent) {
|
override fun modelChanged(event: PomModelEvent) {
|
||||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: return
|
||||||
val file = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
val file = changeSet.rootElement.psi.containingFile as? KtFile ?: return
|
||||||
if (changeSet.changedElements.any { !isInsideCodeBlock(it.psi) }) {
|
if (changeSet.changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null }) {
|
||||||
if (file.isPhysical) {
|
if (file.isPhysical) {
|
||||||
modificationTracker.incCounter()
|
modificationTracker.incCounter()
|
||||||
}
|
}
|
||||||
@@ -67,39 +68,38 @@ class KotlinCodeBlockModificationListener(
|
|||||||
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
|
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isInsideCodeBlock(element: PsiElement): Boolean {
|
fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? {
|
||||||
val lambda = KtPsiUtil.getTopmostParentOfTypes(element, KtLambdaExpression::class.java)
|
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
||||||
if (lambda is KtLambdaExpression) {
|
if (lambda is KtLambdaExpression) {
|
||||||
if (KtPsiUtil.getTopmostParentOfTypes(lambda, KtSuperTypeCallEntry::class.java) != null) {
|
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()
|
||||||
return true
|
?.let { return it }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return false
|
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return null
|
||||||
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return false // should not be local declaration
|
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return null // should not be local declaration
|
||||||
|
|
||||||
when (blockDeclaration) {
|
when (blockDeclaration) {
|
||||||
is KtNamedFunction -> {
|
is KtNamedFunction -> {
|
||||||
if (blockDeclaration.hasBlockBody()) {
|
if (blockDeclaration.hasBlockBody()) {
|
||||||
return blockDeclaration.bodyExpression.isAncestor(element)
|
return blockDeclaration.bodyExpression?.takeIf { it.isAncestor(element) }
|
||||||
}
|
}
|
||||||
else if (blockDeclaration.hasDeclaredReturnType()) {
|
else if (blockDeclaration.hasDeclaredReturnType()) {
|
||||||
return blockDeclaration.initializer.isAncestor(element)
|
return blockDeclaration.initializer?.takeIf { it.isAncestor(element) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is KtProperty -> {
|
is KtProperty -> {
|
||||||
for (accessor in blockDeclaration.accessors) {
|
for (accessor in blockDeclaration.accessors) {
|
||||||
if (accessor.initializer.isAncestor(element) || accessor.bodyExpression.isAncestor(element)) {
|
(accessor.initializer ?: accessor.bodyExpression)
|
||||||
return true
|
?.takeIf { it.isAncestor(element) }
|
||||||
}
|
?.let { return it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
|
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
|
||||||
|
|||||||
+171
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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.completion
|
||||||
|
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiComment
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiErrorElement
|
||||||
|
import com.intellij.psi.PsiWhiteSpace
|
||||||
|
import com.intellij.psi.util.CachedValue
|
||||||
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
|
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||||
|
import org.jetbrains.kotlin.idea.project.KotlinCodeBlockModificationListener
|
||||||
|
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||||
|
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.CompositeBindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
import java.lang.ref.SoftReference
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class CompletionBindingContextProvider(project: Project) {
|
||||||
|
private val LOG = Logger.getInstance(CompletionBindingContextProvider::class.java)
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
internal var TEST_LOG: StringBuilder? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getInstance(project: Project): CompletionBindingContextProvider
|
||||||
|
= project.getComponent(CompletionBindingContextProvider::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CompletionData(
|
||||||
|
val block: KtBlockExpression,
|
||||||
|
val prevStatement: KtExpression?,
|
||||||
|
val psiElementsBeforeAndAfter: List<PsiElementData>,
|
||||||
|
val bindingContext: BindingContext,
|
||||||
|
val statementResolutionScope: LexicalScope,
|
||||||
|
val statementDataFlowInfo: DataFlowInfo,
|
||||||
|
val debugText: String
|
||||||
|
)
|
||||||
|
|
||||||
|
private data class PsiElementData(val element: PsiElement, val level: Int)
|
||||||
|
|
||||||
|
private class DataHolder {
|
||||||
|
private var reference: SoftReference<CompletionData>? = null
|
||||||
|
|
||||||
|
var data: CompletionData?
|
||||||
|
get() = reference?.get()
|
||||||
|
set(value) { reference = value?.let { SoftReference(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private var prevCompletionDataCache: CachedValue<DataHolder> = CachedValuesManager.getManager(project).createCachedValue(
|
||||||
|
{ CachedValueProvider.Result.create(DataHolder(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) },
|
||||||
|
false)
|
||||||
|
|
||||||
|
|
||||||
|
fun getBindingContext(position: PsiElement, resolutionFacade: ResolutionFacade): BindingContext {
|
||||||
|
assert(!position.isPhysical) // position is in synthetic file
|
||||||
|
|
||||||
|
val inStatement = position.findStatementInBlock()
|
||||||
|
val block = inStatement?.parent as KtBlockExpression?
|
||||||
|
val prevStatement = inStatement?.siblings(forward = false, withItself = false)?.firstIsInstanceOrNull<KtExpression>()
|
||||||
|
val modificationScope = inStatement?.let { KotlinCodeBlockModificationListener.getInsideCodeBlockModificationScope(it) }
|
||||||
|
|
||||||
|
val psiElementsBeforeAndAfter = modificationScope?.let { collectPsiElementsBeforeAndAfter(modificationScope, inStatement) }
|
||||||
|
|
||||||
|
val prevCompletionData = prevCompletionDataCache.value.data
|
||||||
|
if (prevCompletionData == null) {
|
||||||
|
log("No up-to-date data from previous completion\n")
|
||||||
|
}
|
||||||
|
else if (block != prevCompletionData.block) {
|
||||||
|
log("Not in the same block\n")
|
||||||
|
}
|
||||||
|
else if (prevStatement != prevCompletionData.prevStatement) {
|
||||||
|
log("Previous statement is not the same\n")
|
||||||
|
}
|
||||||
|
else if (psiElementsBeforeAndAfter != prevCompletionData.psiElementsBeforeAndAfter) {
|
||||||
|
log("PSI-tree has changed inside current scope\n")
|
||||||
|
}
|
||||||
|
else if (inStatement.isTooComplex()) {
|
||||||
|
log("Current statement is too complex to use optimization\n")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log("Statement position is the same - analyzing only one statement:\n${inStatement.text.prependIndent(" ")}\n")
|
||||||
|
LOG.debug("Reusing data from completion of \"${prevCompletionData.debugText}\"")
|
||||||
|
|
||||||
|
//TODO: expected type?
|
||||||
|
val statementContext = inStatement.analyzeInContext(scope = prevCompletionData.statementResolutionScope,
|
||||||
|
contextExpression = block,
|
||||||
|
dataFlowInfo = prevCompletionData.statementDataFlowInfo,
|
||||||
|
isStatement = true,
|
||||||
|
contextDependency = ContextDependency.DEPENDENT)
|
||||||
|
// we do not update prevCompletionDataCache because the same data should work
|
||||||
|
return CompositeBindingContext.create(listOf(statementContext, prevCompletionData.bindingContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
val bindingContext = resolutionFacade.analyze(position.parentsWithSelf.firstIsInstance<KtElement>(), BodyResolveMode.PARTIAL_FOR_COMPLETION)
|
||||||
|
prevCompletionDataCache.value.data = if (block != null && modificationScope != null) {
|
||||||
|
val resolutionScope = inStatement.getResolutionScope(bindingContext, resolutionFacade)
|
||||||
|
val dataFlowInfo = bindingContext.getDataFlowInfoBefore(inStatement)
|
||||||
|
CompletionData(block, prevStatement, psiElementsBeforeAndAfter!!, bindingContext, resolutionScope, dataFlowInfo,
|
||||||
|
debugText = position.text)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindingContext
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun log(message: String) {
|
||||||
|
TEST_LOG?.append(message)
|
||||||
|
LOG.debug(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectPsiElementsBeforeAndAfter(scope: PsiElement, statement: KtExpression): List<PsiElementData> {
|
||||||
|
return ArrayList<PsiElementData>().apply { addElementsInTree(scope, 0, statement) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun MutableList<PsiElementData>.addElementsInTree(root: PsiElement, initialLevel: Int, skipSubtree: PsiElement) {
|
||||||
|
if (root == skipSubtree) return
|
||||||
|
add(PsiElementData(root, initialLevel))
|
||||||
|
var child = root.firstChild
|
||||||
|
while (child != null) {
|
||||||
|
if (child !is PsiWhiteSpace && child !is PsiComment && child !is PsiErrorElement) {
|
||||||
|
addElementsInTree(child, initialLevel + 1, skipSubtree)
|
||||||
|
}
|
||||||
|
child = child.nextSibling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.findStatementInBlock(): KtExpression? {
|
||||||
|
return parents.filterIsInstance<KtExpression>().firstOrNull { it.parent is KtBlockExpression }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtExpression.isTooComplex(): Boolean {
|
||||||
|
return anyDescendantOfType<KtBlockExpression> { it.statements.size > 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
|
|||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.util.*
|
import org.jetbrains.kotlin.idea.util.*
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
@@ -48,7 +47,6 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
|||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
|
||||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class CompletionSessionConfiguration(
|
class CompletionSessionConfiguration(
|
||||||
@@ -104,7 +102,7 @@ abstract class CompletionSession(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val bindingContext = resolutionFacade.analyze(position.parentsWithSelf.firstIsInstance<KtElement>(), BodyResolveMode.PARTIAL_FOR_COMPLETION)
|
protected val bindingContext = CompletionBindingContextProvider.getInstance(project).getBindingContext(position, resolutionFacade)
|
||||||
protected val inDescriptor = position.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor
|
protected val inDescriptor = position.getResolutionScope(bindingContext, resolutionFacade).ownerDescriptor
|
||||||
|
|
||||||
private val kotlinIdentifierStartPattern = StandardPatterns.character().javaIdentifierStart().andNot(singleCharPattern('$'))
|
private val kotlinIdentifierStartPattern = StandardPatterns.character().javaIdentifierStart().andNot(singleCharPattern('$'))
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
fun foo(p1: Int, p2: String): Int {
|
||||||
|
val v1 = p2<change>
|
||||||
|
val v2 = 123
|
||||||
|
return <before><caret>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE: ".hashCode()"
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// EXIST: v1
|
||||||
|
// EXIST: v2
|
||||||
|
// EXIST: p1
|
||||||
|
// ABSENT: p2
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
PSI-tree has changed inside current scope
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
fun foo(p: Int, p1: String): Int {
|
||||||
|
val v1 = p<change>
|
||||||
|
val v2 = 123
|
||||||
|
return <before><caret>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE: "1"
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// ABSENT: v1
|
||||||
|
// EXIST: v2
|
||||||
|
// EXIST: p
|
||||||
|
// ABSENT: p1
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
PSI-tree has changed inside current scope
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
fun foo(p: Any): String {
|
||||||
|
if (p !is String) return ""
|
||||||
|
r<before><change>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE: "eturn "
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// EXIST: p
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Statement position is the same - analyzing only one statement:
|
||||||
|
return IntellijIdeaRulezzz
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
fun foo(p: Any): String {
|
||||||
|
println(1)
|
||||||
|
if (<before><change>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(s: String): Boolean = true
|
||||||
|
|
||||||
|
// TYPE: "p is String && bar("
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// EXIST: p
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Statement position is the same - analyzing only one statement:
|
||||||
|
if (p is String && bar(IntellijIdeaRulezzz$
|
||||||
|
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
fun foo(p1: Int, p2: String): String {
|
||||||
|
val v1 = p1.toString()
|
||||||
|
val v2 = 123
|
||||||
|
if (p1 > 0) {
|
||||||
|
print(1)
|
||||||
|
print(2)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
r<before><change>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE: "eturn "
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// ABSENT: p1
|
||||||
|
// EXIST: p2
|
||||||
|
// EXIST: v1
|
||||||
|
// ABSENT: v2
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Current statement is too complex to use optimization
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
fun foo(p: String?): String {
|
||||||
|
println()
|
||||||
|
bar(p!!, <before><change>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(s: String, p: Int) { }
|
||||||
|
|
||||||
|
// BACKSPACES: 4
|
||||||
|
// TYPE: "."
|
||||||
|
// EXIST: { itemText: "substring", attributes: "grayed" }
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Statement position is the same - analyzing only one statement:
|
||||||
|
bar(p.IntellijIdeaRulezzz$
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fun foo(p1: Int, p2: String): String {
|
||||||
|
r<before><change>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE: "eturn "
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// ABSENT: p1
|
||||||
|
// EXIST: p2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Statement position is the same - analyzing only one statement:
|
||||||
|
return IntellijIdeaRulezzz
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
fun foo(): Int {
|
||||||
|
println()
|
||||||
|
return <before><caret>
|
||||||
|
}
|
||||||
|
|
||||||
|
val xxx<change>
|
||||||
|
|
||||||
|
// TYPE: " = 1"
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// EXIST: xxx
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
No up-to-date data from previous completion
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
fun foo(p1: Any, p2: Any) {
|
||||||
|
if (p1 !is String) return
|
||||||
|
val v1 = "a"
|
||||||
|
val v2 = 123
|
||||||
|
println("a")
|
||||||
|
bar(x<before><change>)
|
||||||
|
if (p2 !is String) return
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar(s: String){}
|
||||||
|
|
||||||
|
// BACKSPACES: 1
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// EXIST: v1
|
||||||
|
// ABSENT: v2
|
||||||
|
// EXIST: p1
|
||||||
|
// ABSENT: p2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Statement position is the same - analyzing only one statement:
|
||||||
|
bar(IntellijIdeaRulezzz$
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
fun foo(p1: Int, p2: String): String {
|
||||||
|
val v1 = p1.toString()
|
||||||
|
val v2 = 123
|
||||||
|
r<before><change>
|
||||||
|
}
|
||||||
|
|
||||||
|
// TYPE: "eturn "
|
||||||
|
// COMPLETION_TYPE: SMART
|
||||||
|
// ABSENT: p1
|
||||||
|
// EXIST: p2
|
||||||
|
// EXIST: v1
|
||||||
|
// ABSENT: v2
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
No up-to-date data from previous completion
|
||||||
|
Statement position is the same - analyzing only one statement:
|
||||||
|
return IntellijIdeaRulezzz
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.completion.test
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.CompletionType
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||||
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
abstract class AbstractCompletionIncrementalResolveTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
|
private val BEFORE_MARKER = "<before>" // position to invoke completion before
|
||||||
|
private val CHANGE_MARKER = "<change>" // position to insert text specified by "TYPE" directive
|
||||||
|
private val TYPE_DIRECTIVE_PREFIX = "// TYPE:"
|
||||||
|
private val BACKSPACES_DIRECTIVE_PREFIX = "// BACKSPACES:"
|
||||||
|
|
||||||
|
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||||
|
|
||||||
|
protected fun doTest(testPath: String) {
|
||||||
|
val file = File(testPath)
|
||||||
|
val hasCaretMarker = FileUtil.loadFile(file, true).contains("<caret>")
|
||||||
|
myFixture.configureByFile(testPath)
|
||||||
|
|
||||||
|
val document = myFixture.editor.document
|
||||||
|
val beforeMarkerOffset = document.text.indexOf(BEFORE_MARKER)
|
||||||
|
assertTrue("\"$BEFORE_MARKER\" is missing in file \"$testPath\"", beforeMarkerOffset >= 0)
|
||||||
|
|
||||||
|
val changeMarkerOffset = document.text.indexOf(CHANGE_MARKER)
|
||||||
|
assertTrue("\"$CHANGE_MARKER\" is missing in file \"$testPath\"", changeMarkerOffset >= 0)
|
||||||
|
|
||||||
|
val textToType = InTextDirectivesUtils.findArrayWithPrefixes(document.text, TYPE_DIRECTIVE_PREFIX).singleOrNull()
|
||||||
|
?.let { StringUtil.unquoteString(it) }
|
||||||
|
val backspaceCount = InTextDirectivesUtils.getPrefixedInt(document.text, BACKSPACES_DIRECTIVE_PREFIX)
|
||||||
|
assertTrue("At least one of \"$TYPE_DIRECTIVE_PREFIX\" and \"$BACKSPACES_DIRECTIVE_PREFIX\" should be defined",
|
||||||
|
textToType != null || backspaceCount != null)
|
||||||
|
|
||||||
|
val beforeMarker = document.createRangeMarker(beforeMarkerOffset, beforeMarkerOffset + BEFORE_MARKER.length)
|
||||||
|
val changeMarker = document.createRangeMarker(changeMarkerOffset, changeMarkerOffset + CHANGE_MARKER.length)
|
||||||
|
changeMarker.isGreedyToRight = true
|
||||||
|
|
||||||
|
project.executeWriteCommand("") {
|
||||||
|
document.deleteString(beforeMarker.startOffset, beforeMarker.endOffset)
|
||||||
|
document.deleteString(changeMarker.startOffset, changeMarker.endOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
val caretMarker = if (hasCaretMarker)
|
||||||
|
document.createRangeMarker(editor.caretModel.offset, editor.caretModel.offset)
|
||||||
|
else
|
||||||
|
null
|
||||||
|
editor.caretModel.moveToOffset(beforeMarker.startOffset)
|
||||||
|
|
||||||
|
val testLog = StringBuilder()
|
||||||
|
CompletionBindingContextProvider.getInstance(project).TEST_LOG = testLog
|
||||||
|
|
||||||
|
myFixture.complete(CompletionType.BASIC)
|
||||||
|
|
||||||
|
project.executeWriteCommand("") {
|
||||||
|
if (backspaceCount != null) {
|
||||||
|
document.deleteString(changeMarker.startOffset - backspaceCount, changeMarker.startOffset)
|
||||||
|
}
|
||||||
|
if (textToType != null) {
|
||||||
|
document.insertString(changeMarker.startOffset, textToType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caretMarker != null) {
|
||||||
|
editor.caretModel.moveToOffset(caretMarker.startOffset)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
editor.caretModel.moveToOffset(changeMarker.endOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCompletion(FileUtil.loadFile(file, true),
|
||||||
|
JvmPlatform,
|
||||||
|
{ completionType, count -> myFixture.complete(completionType, count) },
|
||||||
|
additionalValidDirectives = listOf(TYPE_DIRECTIVE_PREFIX, BACKSPACES_DIRECTIVE_PREFIX))
|
||||||
|
|
||||||
|
KotlinTestUtils.assertEqualsToFile(File(file.parent, file.nameWithoutExtension + ".log"), testLog.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.completion.test;
|
||||||
|
|
||||||
|
import com.intellij.testFramework.TestDataPath;
|
||||||
|
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||||
|
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||||
|
import org.jetbrains.kotlin.test.TargetBackend;
|
||||||
|
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/idea-completion/testData/incrementalResolve")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public class CompletionIncrementalResolveTestGenerated extends AbstractCompletionIncrementalResolveTest {
|
||||||
|
public void testAllFilesPresentInIncrementalResolve() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/idea-completion/testData/incrementalResolve"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("codeAboveChanged.kt")
|
||||||
|
public void testCodeAboveChanged() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/codeAboveChanged.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("codeAboveChanged2.kt")
|
||||||
|
public void testCodeAboveChanged2() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/codeAboveChanged2.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("dataFlowInfoFromPrevStatement.kt")
|
||||||
|
public void testDataFlowInfoFromPrevStatement() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/dataFlowInfoFromPrevStatement.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("dataFlowInfoFromSameStatement.kt")
|
||||||
|
public void testDataFlowInfoFromSameStatement() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/dataFlowInfoFromSameStatement.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("doNotAnalyzeComplexStatement.kt")
|
||||||
|
public void testDoNotAnalyzeComplexStatement() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/doNotAnalyzeComplexStatement.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noDataFlowFromOldStatement.kt")
|
||||||
|
public void testNoDataFlowFromOldStatement() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/noDataFlowFromOldStatement.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noPrevStatement.kt")
|
||||||
|
public void testNoPrevStatement() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/noPrevStatement.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("outOfBlockModification.kt")
|
||||||
|
public void testOutOfBlockModification() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/outOfBlockModification.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("prevStatementNotResolved.kt")
|
||||||
|
public void testPrevStatementNotResolved() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/prevStatementNotResolved.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("sameStatement.kt")
|
||||||
|
public void testSameStatement() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/idea-completion/testData/incrementalResolve/sameStatement.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-2
@@ -27,13 +27,20 @@ val RELATIVE_COMPLETION_TEST_DATA_BASE_PATH = "idea/idea-completion/testData"
|
|||||||
|
|
||||||
val COMPLETION_TEST_DATA_BASE_PATH = KotlinTestUtils.getHomeDirectory() + "/" + RELATIVE_COMPLETION_TEST_DATA_BASE_PATH
|
val COMPLETION_TEST_DATA_BASE_PATH = KotlinTestUtils.getHomeDirectory() + "/" + RELATIVE_COMPLETION_TEST_DATA_BASE_PATH
|
||||||
|
|
||||||
fun testCompletion(fileText: String, platform: TargetPlatform?, complete: (CompletionType, Int) -> Array<LookupElement>?, defaultCompletionType: CompletionType = CompletionType.BASIC, defaultInvocationCount: Int = 0) {
|
fun testCompletion(
|
||||||
|
fileText: String,
|
||||||
|
platform: TargetPlatform?,
|
||||||
|
complete: (CompletionType, Int) -> Array<LookupElement>?,
|
||||||
|
defaultCompletionType: CompletionType = CompletionType.BASIC,
|
||||||
|
defaultInvocationCount: Int = 0,
|
||||||
|
additionalValidDirectives: Collection<String> = emptyList()
|
||||||
|
) {
|
||||||
testWithAutoCompleteSetting(fileText) {
|
testWithAutoCompleteSetting(fileText) {
|
||||||
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
|
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
|
||||||
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
|
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
|
||||||
val items = complete(completionType, invocationCount) ?: emptyArray()
|
val items = complete(completionType, invocationCount) ?: emptyArray()
|
||||||
|
|
||||||
ExpectedCompletionUtils.assertDirectivesValid(fileText)
|
ExpectedCompletionUtils.assertDirectivesValid(fileText, additionalValidDirectives)
|
||||||
|
|
||||||
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
|
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
|
||||||
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
|
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
|
||||||
|
|||||||
+2
-2
@@ -221,8 +221,8 @@ object ExpectedCompletionUtils {
|
|||||||
return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, WITH_ORDER_PREFIX).isEmpty()
|
return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, WITH_ORDER_PREFIX).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertDirectivesValid(fileText: String) {
|
fun assertDirectivesValid(fileText: String, additionalValidDirectives: Collection<String> = emptyList()) {
|
||||||
InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES)
|
InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES + additionalValidDirectives)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertContainsRenderedItems(expected: Array<CompletionProposal>, items: Array<LookupElement>, checkOrder: Boolean, nothingElse: Boolean) {
|
fun assertContainsRenderedItems(expected: Array<CompletionProposal>, items: Array<LookupElement>, checkOrder: Boolean, nothingElse: Boolean) {
|
||||||
|
|||||||
@@ -47,6 +47,10 @@
|
|||||||
<component>
|
<component>
|
||||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||||
</component>
|
</component>
|
||||||
|
<component>
|
||||||
|
<interface-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</interface-class>
|
||||||
|
<implementation-class>org.jetbrains.kotlin.idea.completion.CompletionBindingContextProvider</implementation-class>
|
||||||
|
</component>
|
||||||
</project-components>
|
</project-components>
|
||||||
|
|
||||||
<application-components>
|
<application-components>
|
||||||
|
|||||||
+2
-1
@@ -20,11 +20,12 @@ import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
|||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
|
||||||
class ConcatenatedStringGenerator {
|
class ConcatenatedStringGenerator {
|
||||||
fun create(element: KtBinaryExpression): String {
|
fun create(element: KtBinaryExpression): String {
|
||||||
val binaryExpression = KtPsiUtil.getTopmostParentOfTypes(element, KtBinaryExpression::class.java) as? KtBinaryExpression ?: element
|
val binaryExpression = element.getTopmostParentOfType <KtBinaryExpression>() ?: element
|
||||||
val stringBuilder = StringBuilder()
|
val stringBuilder = StringBuilder()
|
||||||
binaryExpression.appendTo(stringBuilder)
|
binaryExpression.appendTo(stringBuilder)
|
||||||
return stringBuilder.toString()
|
return stringBuilder.toString()
|
||||||
|
|||||||
Reference in New Issue
Block a user