Optimization to reuse BindingContext in completion
This commit is contained in:
@@ -1146,6 +1146,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractJava8BasicCompletionTest> {
|
||||
model("basic/java8")
|
||||
}
|
||||
|
||||
testClass<AbstractCompletionIncrementalResolveTest>() {
|
||||
model("incrementalResolve")
|
||||
}
|
||||
}
|
||||
|
||||
//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.util.PsiModificationTracker
|
||||
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.parents
|
||||
|
||||
@@ -51,7 +52,7 @@ class KotlinCodeBlockModificationListener(
|
||||
override fun modelChanged(event: PomModelEvent) {
|
||||
val changeSet = event.getChangeSet(treeAspect) as TreeChangeEvent? ?: 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) {
|
||||
modificationTracker.incCounter()
|
||||
}
|
||||
@@ -67,39 +68,38 @@ class KotlinCodeBlockModificationListener(
|
||||
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
|
||||
}
|
||||
|
||||
private fun isInsideCodeBlock(element: PsiElement): Boolean {
|
||||
val lambda = KtPsiUtil.getTopmostParentOfTypes(element, KtLambdaExpression::class.java)
|
||||
fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? {
|
||||
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
||||
if (lambda is KtLambdaExpression) {
|
||||
if (KtPsiUtil.getTopmostParentOfTypes(lambda, KtSuperTypeCallEntry::class.java) != null) {
|
||||
return true
|
||||
}
|
||||
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return false
|
||||
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return false // should not be local declaration
|
||||
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) ?: return null
|
||||
if (blockDeclaration.parents.any { it !is KtClassBody && it !is KtClassOrObject && it !is KtFile }) return null // should not be local declaration
|
||||
|
||||
when (blockDeclaration) {
|
||||
is KtNamedFunction -> {
|
||||
if (blockDeclaration.hasBlockBody()) {
|
||||
return blockDeclaration.bodyExpression.isAncestor(element)
|
||||
return blockDeclaration.bodyExpression?.takeIf { it.isAncestor(element) }
|
||||
}
|
||||
else if (blockDeclaration.hasDeclaredReturnType()) {
|
||||
return blockDeclaration.initializer.isAncestor(element)
|
||||
return blockDeclaration.initializer?.takeIf { it.isAncestor(element) }
|
||||
}
|
||||
}
|
||||
|
||||
is KtProperty -> {
|
||||
for (accessor in blockDeclaration.accessors) {
|
||||
if (accessor.initializer.isAncestor(element) || accessor.bodyExpression.isAncestor(element)) {
|
||||
return true
|
||||
}
|
||||
(accessor.initializer ?: accessor.bodyExpression)
|
||||
?.takeIf { it.isAncestor(element) }
|
||||
?.let { return it }
|
||||
}
|
||||
}
|
||||
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
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.util.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
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.checker.KotlinTypeChecker
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import java.util.*
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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) {
|
||||
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
|
||||
val invocationCount = ExpectedCompletionUtils.getInvocationCount(fileText) ?: defaultInvocationCount
|
||||
val items = complete(completionType, invocationCount) ?: emptyArray()
|
||||
|
||||
ExpectedCompletionUtils.assertDirectivesValid(fileText)
|
||||
ExpectedCompletionUtils.assertDirectivesValid(fileText, additionalValidDirectives)
|
||||
|
||||
val expected = ExpectedCompletionUtils.itemsShouldExist(fileText, platform)
|
||||
val unexpected = ExpectedCompletionUtils.itemsShouldAbsent(fileText, platform)
|
||||
|
||||
+2
-2
@@ -221,8 +221,8 @@ object ExpectedCompletionUtils {
|
||||
return !InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, WITH_ORDER_PREFIX).isEmpty()
|
||||
}
|
||||
|
||||
fun assertDirectivesValid(fileText: String) {
|
||||
InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES)
|
||||
fun assertDirectivesValid(fileText: String, additionalValidDirectives: Collection<String> = emptyList()) {
|
||||
InTextDirectivesUtils.assertHasUnknownPrefixes(fileText, KNOWN_PREFIXES + additionalValidDirectives)
|
||||
}
|
||||
|
||||
fun assertContainsRenderedItems(expected: Array<CompletionProposal>, items: Array<LookupElement>, checkOrder: Boolean, nothingElse: Boolean) {
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
<component>
|
||||
<implementation-class>org.jetbrains.kotlin.idea.caches.KotlinPackageContentModificationListener</implementation-class>
|
||||
</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>
|
||||
|
||||
<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.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getTopmostParentOfType
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
|
||||
class ConcatenatedStringGenerator {
|
||||
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()
|
||||
binaryExpression.appendTo(stringBuilder)
|
||||
return stringBuilder.toString()
|
||||
|
||||
Reference in New Issue
Block a user