Provide incremental analysis of a file when it is applicable

#KT32868 Fixed
This commit is contained in:
Vladimir Dolzhenko
2019-07-27 16:18:53 +02:00
parent c7bd6d8ede
commit 4b2834c4a8
68 changed files with 730 additions and 318 deletions
@@ -34,12 +34,12 @@ open class DelegatingBindingTrace(
allowSliceRewrite: Boolean = false
) : BindingTrace {
private val map = if (BindingTraceContext.TRACK_REWRITES && !allowSliceRewrite)
protected val map = if (BindingTraceContext.TRACK_REWRITES && !allowSliceRewrite)
TrackingSlicedMap(BindingTraceContext.TRACK_WITH_STACK_TRACES)
else
SlicedMapImpl(allowSliceRewrite)
private val mutableDiagnostics: MutableDiagnosticsWithSuppression?
protected val mutableDiagnostics: MutableDiagnosticsWithSuppression?
private inner class MyBindingContext : BindingContext {
override fun getDiagnostics(): Diagnostics = mutableDiagnostics ?: Diagnostics.EMPTY
@@ -99,16 +99,15 @@ open class DelegatingBindingTrace(
record(slice, key, true)
}
override fun <K, V> get(slice: ReadOnlySlice<K, V>, key: K): V? {
val value = map.get(slice, key)
if (slice is SetSlice<*>) {
assert(value != null)
if (value != SetSlice.DEFAULT) return value
} else if (value != null) {
return value
}
override fun <K, V> get(slice: ReadOnlySlice<K, V>, key: K): V? =
selfGet(slice, key) ?: parentContext.get(slice, key)
return parentContext.get(slice, key)
protected fun <K, V> selfGet(slice: ReadOnlySlice<K, V>, key: K): V? {
val value = map.get(slice, key)
return if (slice is SetSlice<*>) {
assert(value != null)
if (value != SetSlice.DEFAULT) value else null
} else value
}
override fun <K, V> getKeys(slice: WritableSlice<K, V>): Collection<K> {
@@ -145,7 +144,7 @@ open class DelegatingBindingTrace(
BindingContextUtils.addOwnDataTo(trace, filter, commitDiagnostics, map, mutableDiagnostics)
}
fun clear() {
open fun clear() {
map.clear()
mutableDiagnostics?.clear()
}
@@ -246,24 +246,15 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
val specialModuleInfo = files.map(KtFile::getModuleInfo).toSet().single()
val settings = specialModuleInfo.platformSettings(specialModuleInfo.platform ?: targetPlatform)
// File copies are created during completion and receive correct modification events through POM.
// Dummy files created e.g. by J2K do not receive events.
val filesModificationTracker = if (files.all { it.originalFile != it }) {
ModificationTracker {
files.sumByLong { it.outOfBlockModificationCount }
}
val dependenciesForSyntheticFileCache = if (files.all { it.originalFile != it }) {
emptyList()
} else {
ModificationTracker {
files.sumByLong { it.outOfBlockModificationCount + it.modificationStamp }
}
listOf(ModificationTracker {
files.sumByLong { it.modificationStamp }
})
}
val dependenciesForSyntheticFileCache =
listOf(
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker,
filesModificationTracker
)
val resolverDebugName =
"$resolverForSpecialInfoName $specialModuleInfo for files ${files.joinToString { it.name }} for platform $targetPlatform"
@@ -16,12 +16,15 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.google.common.collect.ImmutableMap
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.*
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
@@ -29,18 +32,25 @@ import org.jetbrains.kotlin.context.GlobalContext
import org.jetbrains.kotlin.context.withModule
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.clearInBlockModifications
import org.jetbrains.kotlin.idea.caches.trackers.inBlockModifications
import org.jetbrains.kotlin.idea.project.IdeaModuleStructureOracle
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticsElementsCache
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import org.jetbrains.kotlin.util.slicedMap.WritableSlice
import java.util.*
internal class PerFileAnalysisCache(val file: KtFile, componentProvider: ComponentProvider) {
@@ -51,6 +61,89 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
private val bodyResolveCache = componentProvider.get<BodyResolveCache>()
private val cache = HashMap<PsiElement, AnalysisResult>()
private var fileResult: AnalysisResult? = null
fun getAnalysisResults(element: KtElement): AnalysisResult {
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" }
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element)
return synchronized(this) {
ProgressIndicatorProvider.checkCanceled()
// step 1: perform incremental analysis IF it is applicable
getIncrementalAnalysisResult()?.let { return it }
// cache does not contain AnalysisResult per each kt/psi element
// instead it looks up analysis for its parents - see lookUp(analyzableElement)
// step 2: return result if it is cached
lookUp(analyzableParent)?.let {
return@synchronized it
}
// step 3: perform analyze of analyzableParent as nothing has been cached yet
val result = analyze(analyzableParent)
cache[analyzableParent] = result
return@synchronized result
}
}
private fun getIncrementalAnalysisResult(): AnalysisResult? {
// move fileResult from cache if it is stored there
if (fileResult == null && cache.containsKey(file)) {
fileResult = cache[file]
// drop existed results for entire cache:
// if incremental analysis is applicable it will produce a single value for file
// otherwise those results are potentially stale
cache.clear()
}
val inBlockModifications = file.inBlockModifications
if (inBlockModifications.isNotEmpty()) {
try {
// IF there is a cached result for ktFile and there are inBlockModifications
fileResult = fileResult?.let { result ->
var analysisResult = result
for (inBlockModification in inBlockModifications) {
val resultCtx = analysisResult.bindingContext
val stackedCtx =
if (resultCtx is StackedCompositeBindingContextTrace.StackedCompositeBindingContext) resultCtx else null
// no incremental analysis IF it is not applicable
if (stackedCtx?.isIncrementalAnalysisApplicable() == false) return@let null
val trace: StackedCompositeBindingContextTrace =
if (stackedCtx != null && stackedCtx.element() == inBlockModification) {
val trace = stackedCtx.bindingTrace()
trace.clear()
trace
} else {
// to reflect a depth of stacked binding context
val depth = (stackedCtx?.depth() ?: 0) + 1
StackedCompositeBindingContextTrace(
depth,
element = inBlockModification,
resolveContext = resolveSession.bindingContext,
parentContext = resultCtx
)
}
val newResult = analyze(inBlockModification, trace)
analysisResult = wrapResult(result, newResult, trace)
}
analysisResult
}
} finally {
file.clearInBlockModifications()
}
}
return fileResult
}
private fun lookUp(analyzableElement: KtElement): AnalysisResult? {
// Looking for parent elements that are already analyzed
@@ -75,25 +168,26 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
return result
}
fun getAnalysisResults(element: KtElement): AnalysisResult {
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" }
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element)
return synchronized<AnalysisResult>(this) {
val cached = lookUp(analyzableParent)
if (cached != null) return@synchronized cached
val result = analyze(analyzableParent)
cache[analyzableParent] = result
return@synchronized result
private fun wrapResult(
oldResult: AnalysisResult,
newResult: AnalysisResult,
elementBindingTrace: StackedCompositeBindingContextTrace
): AnalysisResult {
val newBindingCtx = elementBindingTrace.stackedContext
return when {
oldResult.isError() -> AnalysisResult.internalError(newBindingCtx, oldResult.error)
newResult.isError() -> AnalysisResult.internalError(newBindingCtx, newResult.error)
else -> AnalysisResult.success(
newBindingCtx,
oldResult.moduleDescriptor,
oldResult.shouldGenerateCode
)
}
}
private fun analyze(analyzableElement: KtElement): AnalysisResult {
private fun analyze(analyzableElement: KtElement, bindingTrace: BindingTrace? = null): AnalysisResult {
ProgressIndicatorProvider.checkCanceled()
val project = analyzableElement.project
if (DumbService.isDumb(project)) {
return AnalysisResult.EMPTY
@@ -107,7 +201,8 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
resolveSession,
codeFragmentAnalyzer,
bodyResolveCache,
analyzableElement
analyzableElement,
bindingTrace
)
} catch (e: ProcessCanceledException) {
throw e
@@ -122,6 +217,99 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
}
}
private class MergedDiagnostics(val diagnostics: Collection<Diagnostic>, override val modificationTracker: ModificationTracker) : Diagnostics {
@Suppress("UNCHECKED_CAST")
private val elementsCache = DiagnosticsElementsCache(this) { true }
override fun all() = diagnostics
override fun forElement(psiElement: PsiElement): MutableCollection<Diagnostic> = elementsCache.getDiagnostics(psiElement)
override fun noSuppression() = this
}
private class StackedCompositeBindingContextTrace(
val depth: Int, // depth of stack over original ktFile bindingContext
val element: KtElement,
val resolveContext: BindingContext,
val parentContext: BindingContext
) : DelegatingBindingTrace(
resolveContext,
"Stacked trace for resolution of $element",
allowSliceRewrite = true
) {
/**
* Effectively StackedCompositeBindingContext holds up-to-date and partially outdated contexts (parentContext)
*
* The most up-to-date results for element are stored here (in a DelegatingBindingTrace#map)
*
* Note: It does not delete outdated results rather hide it therefore there is some extra memory footprint.
*
* Note: stackedContext differs from DelegatingBindingTrace#bindingContext:
* if result is not present in this context it goes to parentContext rather to resolveContext
* diagnostics are aggregated from this context and parentContext
*/
val stackedContext = StackedCompositeBindingContext()
/**
* All diagnostics from parentContext apart those diagnostics those belongs to the element or its descendants
*/
val parentDiagnosticsApartElement: List<Diagnostic> = parentContext.diagnostics.all().filter { d ->
d.psiElement.parentsWithSelf.none { it == element }
}.toList()
inner class StackedCompositeBindingContext : BindingContext {
var cachedDiagnostics: Diagnostics? = null
fun bindingTrace(): StackedCompositeBindingContextTrace = this@StackedCompositeBindingContextTrace
fun element(): KtElement = this@StackedCompositeBindingContextTrace.element
fun depth(): Int = this@StackedCompositeBindingContextTrace.depth
// to prevent too deep stacked binding context
fun isIncrementalAnalysisApplicable(): Boolean = this@StackedCompositeBindingContextTrace.depth < 16
override fun getDiagnostics(): Diagnostics {
if (cachedDiagnostics == null) {
val diagnosticList =
parentDiagnosticsApartElement + (this@StackedCompositeBindingContextTrace.mutableDiagnostics?.all() ?: emptyList())
cachedDiagnostics = MergedDiagnostics(diagnosticList, parentContext.diagnostics.modificationTracker)
}
return cachedDiagnostics!!
}
override fun <K : Any?, V : Any?> get(slice: ReadOnlySlice<K, V>, key: K): V? {
return selfGet(slice, key) ?: parentContext.get(slice, key)
}
override fun getType(expression: KtExpression): KotlinType? {
val typeInfo = get(BindingContext.EXPRESSION_TYPE_INFO, expression)
return typeInfo?.type
}
override fun <K, V> getKeys(slice: WritableSlice<K, V>): Collection<K> {
val keys = map.getKeys(slice)
val fromParent = parentContext.getKeys(slice)
if (keys.isEmpty()) return fromParent
if (fromParent.isEmpty()) return keys
return keys + fromParent
}
override fun <K : Any?, V : Any?> getSliceContents(slice: ReadOnlySlice<K, V>): ImmutableMap<K, V> {
return ImmutableMap.copyOf(parentContext.getSliceContents(slice) + map.getSliceContents(slice))
}
override fun addOwnDataTo(trace: BindingTrace, commitDiagnostics: Boolean) = throw UnsupportedOperationException()
}
override fun clear() {
super.clear()
stackedContext.cachedDiagnostics = null
}
}
private object KotlinResolveDataProvider {
private val topmostElementTypes = arrayOf<Class<out PsiElement?>?>(
KtNamedFunction::class.java,
@@ -159,9 +347,9 @@ private object KotlinResolveDataProvider {
if (analyzableElement is KtClassInitializer) return analyzableElement.containingDeclaration
return analyzableElement
// if none of the above worked, take the outermost declaration
?: PsiTreeUtil.getTopmostParentOfType(element, KtDeclaration::class.java)
// if even that didn't work, take the whole file
?: element.containingKtFile
?: PsiTreeUtil.getTopmostParentOfType(element, KtDeclaration::class.java)
// if even that didn't work, take the whole file
?: element.containingKtFile
}
fun analyze(
@@ -171,7 +359,8 @@ private object KotlinResolveDataProvider {
resolveSession: ResolveSession,
codeFragmentAnalyzer: CodeFragmentAnalyzer,
bodyResolveCache: BodyResolveCache,
analyzableElement: KtElement
analyzableElement: KtElement,
bindingTrace: BindingTrace?
): AnalysisResult {
try {
if (analyzableElement is KtCodeFragment) {
@@ -180,6 +369,16 @@ private object KotlinResolveDataProvider {
return AnalysisResult.success(bindingContext, moduleDescriptor)
}
val trace = bindingTrace ?: DelegatingBindingTrace(
resolveSession.bindingContext,
"Trace for resolution of $analyzableElement",
allowSliceRewrite = true
)
val moduleInfo = analyzableElement.containingKtFile.getModuleInfo()
val targetPlatform = moduleInfo.platform
/*
Note that currently we *have* to re-create LazyTopDownAnalyzer with custom trace in order to disallow resolution of
bodies in top-level trace (trace from DI-container).
@@ -190,17 +389,6 @@ private object KotlinResolveDataProvider {
(see 'functionAdditionalResolve'). However, this trace is still needed, because we have other
codepaths for other KtDeclarationWithBodies (like property accessors/secondary constructors/class initializers)
*/
val trace = DelegatingBindingTrace(
resolveSession.bindingContext,
"Trace for resolution of " + analyzableElement,
allowSliceRewrite = true
)
val moduleInfo = analyzableElement.containingKtFile.getModuleInfo()
// TODO: should return proper platform!
val targetPlatform = moduleInfo.platform ?: TargetPlatformDetector.getPlatform(analyzableElement.containingKtFile)
val lazyTopDownAnalyzer = createContainerForLazyBodyResolve(
//TODO: should get ModuleContext
globalContext.withProject(project).withModule(moduleDescriptor),
@@ -20,7 +20,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.SLRUCache
import org.jetbrains.kotlin.analyzer.*
import org.jetbrains.kotlin.context.GlobalContextImpl
@@ -28,6 +27,7 @@ import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompositeBindingContext
@@ -69,7 +69,8 @@ internal class ProjectResolutionFacade(
}
}
val allDependencies = resolverForProjectDependencies + listOf(PsiModificationTracker.MODIFICATION_COUNT)
val allDependencies =
resolverForProjectDependencies + listOf(KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker)
CachedValueProvider.Result.create(results, allDependencies)
}, false
)
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.caches.trackers
import com.intellij.lang.ASTNode
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker
@@ -16,10 +17,7 @@ import com.intellij.pom.event.PomModelEvent
import com.intellij.pom.event.PomModelListener
import com.intellij.pom.tree.TreeAspect
import com.intellij.pom.tree.events.TreeChangeEvent
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiTreeChangeEvent
import com.intellij.psi.*
import com.intellij.psi.impl.PsiManagerImpl
import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl
@@ -89,9 +87,17 @@ class KotlinCodeBlockModificationListener(
incFileModificationCount(ktFile)
val changedElements = changeSet.changedElements
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } || changedElements.isEmpty()) {
// skip change if it contains only virtual/fake change
if (changedElements.isNotEmpty() &&
// ignore formatting (whitespaces etc)
(isFormattingChange(changeSet) ||
changedElements.all { !it.psi.isPhysical })
) return
val inBlockChange = inBlockModifications(changedElements)
if (!inBlockChange) {
messageBusConnection.deliverImmediately()
if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) {
@@ -155,6 +161,8 @@ class KotlinCodeBlockModificationListener(
}
private fun incOutOfBlockModificationCount(file: KtFile) {
file.clearInBlockModifications()
val count = file.getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
}
@@ -165,32 +173,72 @@ class KotlinCodeBlockModificationListener(
tracker.incModificationCount()
}
fun getInsideCodeBlockModificationScope(element: PsiElement): KtElement? {
private fun inBlockModifications(elements: Array<ASTNode>): Boolean {
// When a code fragment is reparsed, Intellij doesn't do an AST diff and considers the entire
// contents to be replaced, which is represented in a POM event as an empty list of changed elements
if (elements.isEmpty()) return false
val inBlockElements = mutableSetOf<KtElement>()
for (element in elements) {
// skip fake PSI elements like `IntellijIdeaRulezzz$`
val psi = element.psi
if (!psi.isPhysical) continue
val modificationScope = getInsideCodeBlockModificationScope(psi) ?: return false
inBlockElements.add(modificationScope.blockDeclaration)
}
inBlockElements.forEach { it.containingKtFile.addInBlockModifiedItem(it) }
return inBlockElements.isNotEmpty()
}
fun isFormattingChange(changeSet: TreeChangeEvent): Boolean =
changeSet.changedElements.all {
changeSet.getChangesByElement(it).affectedChildren.all { c -> (c is PsiWhiteSpace || c is PsiComment) }
}
fun getInsideCodeBlockModificationScope(element: PsiElement): BlockModificationScopeElement? {
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
if (lambda is KtLambdaExpression) {
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.let {
return it
return BlockModificationScopeElement(it, it)
}
}
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
if (KtPsiUtil.isLocal(blockDeclaration)) return null // should not be local declaration
val blockDeclaration =
KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
// should not be local declaration
if (KtPsiUtil.isLocal(blockDeclaration))
return null
when (blockDeclaration) {
is KtNamedFunction -> {
if (blockDeclaration.hasBlockBody()) {
return blockDeclaration.bodyExpression?.takeIf { it.isAncestor(element) }
// case like `fun foo(): String {...<caret>...}`
return blockDeclaration.bodyExpression
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
} else if (blockDeclaration.hasDeclaredReturnType()) {
return blockDeclaration.initializer?.takeIf { it.isAncestor(element) }
// case like `fun foo(): String = b<caret>labla`
return blockDeclaration.initializer
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
}
}
is KtProperty -> {
if (blockDeclaration.typeReference != null) {
for (accessor in blockDeclaration.accessors) {
(accessor.initializer ?: accessor.bodyExpression)
?.takeIf { it.isAncestor(element) }
?.let { return it }
val accessors =
blockDeclaration.accessors.map { it.initializer ?: it.bodyExpression } + blockDeclaration.initializer
for (accessor in accessors) {
accessor?.takeIf {
it.isAncestor(element) &&
// adding annotations to accessor is the same as change contract of property
(element !is KtAnnotated || element.annotationEntries.isEmpty())
}
?.let { return BlockModificationScopeElement(blockDeclaration, it) }
}
}
}
@@ -201,14 +249,37 @@ class KotlinCodeBlockModificationListener(
?.lastOrNull()
?.getLambdaExpression()
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
}
is KtClassInitializer -> {
blockDeclaration
.takeIf { it.isAncestor(element) }
?.let { ktClassInitializer ->
(KtPsiUtil.getTopmostParentOfTypes(blockDeclaration, KtClass::class.java) as? KtElement)?.let {
return BlockModificationScopeElement(it, ktClassInitializer)
}
}
}
// TODO: still under consideration - is it worth to track changes of private properties / methods
// problem could be in diagnostics - it is worth to manage it with modTracker
// is KtClass -> {
// return when (element) {
// is KtProperty -> if (element.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) blockDeclaration else null
// is KtNamedFunction -> if (element.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) blockDeclaration else null
// else -> null
// }
// }
else -> throw IllegalStateException()
}
return null
}
data class BlockModificationScopeElement(val blockDeclaration: KtElement, val element: KtElement)
fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) }
}
@@ -216,6 +287,7 @@ class KotlinCodeBlockModificationListener(
private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>(
KtProperty::class.java,
KtNamedFunction::class.java,
KtClassInitializer::class.java,
KtScriptInitializer::class.java
)
@@ -231,6 +303,32 @@ val KtFile.perFileModificationTracker: ModificationTracker
private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
val KtFile.outOfBlockModificationCount: Long
get() = getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
val KtFile.outOfBlockModificationCount: Long by NotNullableUserDataProperty(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, 0)
/**
* inBlockModifications is a collection of block elements those have in-block modifications
*/
private val IN_BLOCK_MODIFICATIONS = Key<MutableCollection<KtElement>>("IN_BLOCK_MODIFICATIONS")
val KtFile.inBlockModifications: Collection<KtElement>
get() {
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
return collection ?: emptySet()
}
private fun KtFile.addInBlockModifiedItem(element: KtElement) {
val collection = putUserDataIfAbsent(IN_BLOCK_MODIFICATIONS, mutableSetOf())
synchronized(collection) {
collection.add(element)
}
}
fun KtFile.clearInBlockModifications() {
val collection = getUserData(IN_BLOCK_MODIFICATIONS)
collection?.let {
synchronized(it) {
it.clear()
}
}
}
@@ -18,18 +18,13 @@ package org.jetbrains.kotlin.idea.highlighter
import com.intellij.codeInsight.daemon.ChangeLocalityDetector
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener.Companion.getInsideCodeBlockModificationScope
class KotlinChangeLocalityDetector : ChangeLocalityDetector {
override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? {
val parent = element.parent
if (element is KtBlockExpression && parent is KtNamedFunction && parent.name != null) {
if (parent.parents.all { it is KtClassBody || it is KtClassOrObject || it is KtFile || it is KtScript }) {
return parent
}
}
val modificationScope =
getInsideCodeBlockModificationScope(element) ?: return null
return null
return modificationScope.blockDeclaration
}
}
@@ -107,7 +107,7 @@ class CompletionBindingContextProvider(project: Project) {
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 modificationScope = inStatement?.let { KotlinCodeBlockModificationListener.getInsideCodeBlockModificationScope(it)?.element }
val psiElementsBeforeAndAfter = modificationScope?.let { collectPsiElementsBeforeAndAfter(modificationScope, inStatement) }
@@ -19,13 +19,16 @@ import org.jetbrains.kotlin.utils.addToStdlib.indexOfOrNull
import java.io.File
abstract class AbstractCompletionHandlerTest(private val defaultCompletionType: CompletionType) : CompletionHandlerTestBase() {
private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
private val LOOKUP_STRING_PREFIX = "ELEMENT:"
private val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:"
private val TAIL_TEXT_PREFIX = "TAIL_TEXT:"
private val COMPLETION_CHAR_PREFIX = "CHAR:"
private val COMPLETION_CHARS_PREFIX = "CHARS:"
private val CODE_STYLE_SETTING_PREFIX = "CODE_STYLE_SETTING:"
companion object {
val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
val LOOKUP_STRING_PREFIX = "ELEMENT:"
val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:"
val TAIL_TEXT_PREFIX = "TAIL_TEXT:"
val COMPLETION_CHAR_PREFIX = "CHAR:"
val COMPLETION_CHARS_PREFIX = "CHARS:"
val CODE_STYLE_SETTING_PREFIX = "CODE_STYLE_SETTING:"
}
protected open fun doTest(testPath: String) {
setUpFixture(fileName())
@@ -42,11 +45,7 @@ abstract class AbstractCompletionHandlerTest(private val defaultCompletionType:
val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX)
val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX)
val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX)
val completionChars = completionChars(
char = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX),
chars = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHARS_PREFIX)
)
val completionChars = completionChars(fileText)
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
@@ -58,8 +57,7 @@ abstract class AbstractCompletionHandlerTest(private val defaultCompletionType:
val settingValue = line.substring(index + 1).trim()
val (field, settings) = try {
kotlinStyleSettings::class.java.getField(settingName) to kotlinStyleSettings
}
catch (e: NoSuchFieldException) {
} catch (e: NoSuchFieldException) {
commonStyleSettings::class.java.getField(settingName) to commonStyleSettings
}
when (field.type.name) {
@@ -70,6 +68,7 @@ abstract class AbstractCompletionHandlerTest(private val defaultCompletionType:
}
doTestWithTextLoaded(
myFixture,
completionType,
invocationCount,
lookupString,
@@ -31,7 +31,16 @@ class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
private fun doTest(time: Int, lookupString: String?, itemText: String?, tailText: String?, completionChar: Char) {
fixture.configureByFile(fileName())
doTestWithTextLoaded(CompletionType.BASIC, time, lookupString, itemText, tailText, completionChar.toString(), fileName() + ".after")
doTestWithTextLoaded(
myFixture,
CompletionType.BASIC,
time,
lookupString,
itemText,
tailText,
completionChar.toString(),
fileName() + ".after"
)
}
fun testClassCompletionImport() = doTest(2, "SortedSet", null, '\n')
@@ -11,114 +11,133 @@ import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.openapi.project.Project
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.completion.test.ExpectedCompletionUtils
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.test.InTextDirectivesUtils
abstract class CompletionHandlerTestBase : KotlinLightCodeInsightFixtureTestCase() {
protected val fixture: JavaCodeInsightTestFixture
get() = myFixture
protected fun doTestWithTextLoaded(
completionType: CompletionType,
time: Int,
lookupString: String?,
itemText: String?,
tailText: String?,
completionChars: String,
afterFilePath: String
) {
for (idx in 0 until completionChars.length - 1) {
fixture.type(completionChars[idx])
companion object {
fun doTestWithTextLoaded(
fixture: JavaCodeInsightTestFixture,
completionType: CompletionType,
time: Int,
lookupString: String?,
itemText: String?,
tailText: String?,
completionChars: String,
afterFilePath: String,
actions: List<String>? = emptyList(),
afterTypingBlock: () -> Unit = {}
) {
for (idx in 0 until completionChars.length - 1) {
fixture.type(completionChars[idx])
afterTypingBlock()
}
if (actions != null && actions.isNotEmpty()) {
for (action in actions) {
fixture.performEditorAction(action)
}
}
fixture.complete(completionType, time)
if (lookupString != null || itemText != null || tailText != null) {
val item = getExistentLookupElement(fixture.project, lookupString, itemText, tailText)
if (item != null) {
selectItem(fixture, item, completionChars.last())
}
}
afterTypingBlock()
fixture.checkResultByFile(afterFilePath)
}
fixture.complete(completionType, time)
if (lookupString != null || itemText != null || tailText != null) {
val item = getExistentLookupElement(lookupString, itemText, tailText)
if (item != null) {
selectItem(item, completionChars.last())
fun completionChars(text: String): String {
val char: String? = InTextDirectivesUtils.findStringWithPrefixes(text, AbstractCompletionHandlerTest.COMPLETION_CHAR_PREFIX)
val chars: String? = InTextDirectivesUtils.findStringWithPrefixes(text, AbstractCompletionHandlerTest.COMPLETION_CHARS_PREFIX)
return when (char) {
null -> when (chars) {
null -> "\n"
else -> chars.replace("\\n", "\n").replace("\\t", "\t")
}
"\\n" -> "\n"
"\\t" -> "\t"
else -> char.single().toString() ?: error("Incorrect completion char: \"$char\"")
}
}
fixture.checkResultByFile(afterFilePath)
}
protected fun completionChars(char: String?, chars: String?): String =
when (char) {
null -> when (chars) {
null -> "\n"
else -> chars.replace("\\n", "\n").replace("\\t", "\t")
fun getExistentLookupElement(project: Project, lookupString: String?, itemText: String?, tailText: String?): LookupElement? {
val lookup = LookupManager.getInstance(project)?.activeLookup as LookupImpl? ?: return null
val items = lookup.items
if (lookupString == "*") {
assert(itemText == null)
assert(tailText == null)
return items.firstOrNull()
}
"\\n" -> "\n"
"\\t" -> "\t"
else -> char.single().toString() ?: error("Incorrect completion char: \"$char\"")
}
protected fun getExistentLookupElement(lookupString: String?, itemText: String?, tailText: String?): LookupElement? {
val lookup = LookupManager.getInstance(project)?.activeLookup as LookupImpl? ?: return null
val items = lookup.items
var foundElement : LookupElement? = null
val presentation = LookupElementPresentation()
for (lookupElement in items) {
val lookupOk = if (lookupString != null) lookupElement.lookupString == lookupString else true
if (lookupString == "*") {
assert(itemText == null)
assert(tailText == null)
return items.firstOrNull()
}
if (lookupOk) {
lookupElement.renderElement(presentation)
var foundElement : LookupElement? = null
val presentation = LookupElementPresentation()
for (lookupElement in items) {
val lookupOk = if (lookupString != null) lookupElement.lookupString == lookupString else true
if (lookupOk) {
lookupElement.renderElement(presentation)
val textOk = if (itemText != null) {
val itemItemText = presentation.itemText
itemItemText != null && itemItemText == itemText
}
else {
true
}
if (textOk) {
val tailOk = if (tailText != null) {
val itemTailText = presentation.tailText
itemTailText != null && itemTailText == tailText
val textOk = if (itemText != null) {
val itemItemText = presentation.itemText
itemItemText != null && itemItemText == itemText
}
else {
true
}
if (tailOk) {
if (foundElement != null) {
val dump = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(arrayOf(foundElement, lookupElement)))
fail("Several elements satisfy to completion restrictions:\n$dump")
if (textOk) {
val tailOk = if (tailText != null) {
val itemTailText = presentation.tailText
itemTailText != null && itemTailText == tailText
}
else {
true
}
foundElement = lookupElement
if (tailOk) {
if (foundElement != null) {
val dump = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(arrayOf(foundElement, lookupElement)))
fail("Several elements satisfy to completion restrictions:\n$dump")
}
foundElement = lookupElement
}
}
}
}
if (foundElement == null) {
val dump = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items.toTypedArray()))
error("No element satisfy completion restrictions in:\n$dump")
}
return foundElement
}
if (foundElement == null) {
val dump = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items.toTypedArray()))
error("No element satisfy completion restrictions in:\n$dump")
}
return foundElement
}
protected fun selectItem(item: LookupElement?, completionChar: Char) {
val lookup = (fixture.lookup as LookupImpl)
if (lookup.currentItem != item) { // do not touch selection if not changed - important for char filter tests
lookup.currentItem = item
}
lookup.focusDegree = LookupImpl.FocusDegree.FOCUSED
if (LookupEvent.isSpecialCompletionChar(completionChar)) {
lookup.finishLookup(completionChar)
}
else {
fixture.type(completionChar)
fun selectItem(fixture: JavaCodeInsightTestFixture, item: LookupElement?, completionChar: Char) {
val lookup = (fixture.lookup as LookupImpl)
if (lookup.currentItem != item) { // do not touch selection if not changed - important for char filter tests
lookup.currentItem = item
}
lookup.focusDegree = LookupImpl.FocusDegree.FOCUSED
if (LookupEvent.isSpecialCompletionChar(completionChar)) {
lookup.finishLookup(completionChar)
}
else {
fixture.type(completionChar)
}
}
}
}
@@ -12,6 +12,13 @@ import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.completion.test.ExpectedCompletionUtils
import org.jetbrains.kotlin.idea.completion.test.configureWithExtraFile
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.CODE_STYLE_SETTING_PREFIX
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.COMPLETION_CHARS_PREFIX
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.COMPLETION_CHAR_PREFIX
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.ELEMENT_TEXT_PREFIX
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.INVOCATION_COUNT_PREFIX
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.LOOKUP_STRING_PREFIX
import org.jetbrains.kotlin.idea.completion.test.handlers.AbstractCompletionHandlerTest.Companion.TAIL_TEXT_PREFIX
import org.jetbrains.kotlin.idea.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.configureCompilerOptions
@@ -29,14 +36,6 @@ abstract class AbstractPerformanceCompletionHandlerTests(
private val note: String = ""
) : CompletionHandlerTestBase() {
private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
private val LOOKUP_STRING_PREFIX = "ELEMENT:"
private val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:"
private val TAIL_TEXT_PREFIX = "TAIL_TEXT:"
private val COMPLETION_CHAR_PREFIX = "CHAR:"
private val COMPLETION_CHARS_PREFIX = "CHARS:"
private val CODE_STYLE_SETTING_PREFIX = "CODE_STYLE_SETTING:"
companion object {
@JvmStatic
val statsMap: MutableMap<String, Stats> = mutableMapOf()
@@ -74,11 +73,7 @@ abstract class AbstractPerformanceCompletionHandlerTests(
val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX)
val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX)
val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX)
val completionChars = completionChars(
char = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX),
chars = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHARS_PREFIX)
)
val completionChars = completionChars(fileText)
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
@@ -157,9 +152,9 @@ abstract class AbstractPerformanceCompletionHandlerTests(
fixture.complete(completionType, time)
if (lookupString != null || itemText != null || tailText != null) {
val item = getExistentLookupElement(lookupString, itemText, tailText)
val item = getExistentLookupElement(project, lookupString, itemText, tailText)
if (item != null) {
selectItem(item, completionChars.last())
selectItem(fixture, item, completionChars.last())
}
}
}
+4
View File
@@ -0,0 +1,4 @@
// OUT_OF_CODE_BLOCK: FALSE
fun some(): Int = run { 12 + <caret> }
// TYPE: 1
+1 -1
View File
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun main() {
fun some = 12<caret>
}
+2 -5
View File
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: FALSE
class A {
init {
@@ -6,7 +6,4 @@ class A {
<caret>
}
}
}
// TODO
// SKIP_ANALYZE_CHECK
}
+1 -1
View File
@@ -1,2 +1,2 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
fun some() = <caret>12
+1 -1
View File
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
fun test() {<caret>
}
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun test() {
class Test {
<caret>
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
// (Investigation starts from parent)
fun test() : Int = <caret>12
@@ -1,3 +1,3 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun test() : Int = 12 + <caret>12
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
val o = object {
fun test() {
<caret>
+1 -1
View File
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
class Some {
fun <caret>
}
@@ -0,0 +1,11 @@
// OUT_OF_CODE_BLOCK: TRUE
class A {
fun foo(): Int = 12
fun bar(): Int = foo() + <caret>
}
// TYPE: 1
// TODO
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
class Test {
val more : Int = 0
val test : Int
@@ -0,0 +1,11 @@
// OUT_OF_CODE_BLOCK: FALSE
class A {
fun foo(): Int = 12
}
fun A.bar(): Int {
return foo() + <caret>
}
// TYPE: 1
@@ -0,0 +1,9 @@
// OUT_OF_CODE_BLOCK: TRUE
class A {
fun foo(): Int = 12
}
fun A.bar() = foo() + <caret>
// TYPE: 1
@@ -0,0 +1,11 @@
// OUT_OF_CODE_BLOCK: TRUE
class A {
fun foo(): Int = 12
}
fun A.bar(): Int = foo() + <caret>
// TYPE: 1
// TODO
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
val a = 1
fun test() = if (a) {
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
data class Test(val a : Int, val b : Int)
+1 -1
View File
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun test() {
val some = if () {
fun other() {
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
interface Some
fun test() {
+1 -1
View File
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
interface Some
fun test() {
@@ -1,2 +0,0 @@
// TRUE
fun more() = { println<caret> }
+1 -1
View File
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun main() {
jq() { pri<caret> }
@@ -1,5 +1,4 @@
// FALSE
// Important for 173 branch! OOCB is TRUE in this test because of IDEA-185462
// OUT_OF_CODE_BLOCK: FALSE
class B(val a: A)
val B.foo: Int
@@ -0,0 +1,9 @@
// OUT_OF_CODE_BLOCK: FALSE
fun twice(s: String): String {
val repeatFun: String.(Int) -> String = { t -> this.repeat(<caret>) }
return repeatFun(s, 2)
}
// TYPE: t
+1 -1
View File
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun test() {
val a = 1<caret>
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
class Test {
class Other {
<caret>
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
class Test {
class Other {
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
class Test {
class Other {
@@ -0,0 +1,7 @@
// OUT_OF_CODE_BLOCK: TRUE
// TYPE: 'Int`
class InPropertyAccessorSpecifyType {
val prop1: Int
get():<caret> = 42
}
@@ -0,0 +1,8 @@
// OUT_OF_CODE_BLOCK: TRUE
// TYPE: '@Throws(Exception::class)`
class InPropertyAccessorWithAnnotation {
val prop1: Int
<caret>
get() = 42
}
@@ -1,6 +1,6 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
val test : Int
get() = <caret>12
// TODO
// SKIP_ANALYZE_CHECK
// TODO: Investigate
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
class A {
fun foo(): Int = 12
@@ -0,0 +1,10 @@
// OUT_OF_CODE_BLOCK: FALSE
class A {
fun foo(): Int = 12
}
class B(val a: A) {
val prop1: Int
get() = a.fo<caret>o()
}
@@ -1,6 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: FALSE
class Test {
val a : () -> Int = { <caret>pri }
}
// SKIP_ANALYZE_CHECK
}
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
class Test {
val a = "aasdf<caret>"
}
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
// Navigation from "class B: A()" should move to valid constructor even after changing type in lambda
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
open class A(a: () -> Unit)
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
open class A(a: () -> Unit) {
constructor(f: (String) -> Unit) : this({ -> f("") })
+2
View File
@@ -0,0 +1,2 @@
// OUT_OF_CODE_BLOCK: FALSE
fun more() { println<caret> }
+2 -5
View File
@@ -1,10 +1,7 @@
// TRUE
// OUT_OF_CODE_BLOCK: FALSE
class A {
init {
ca<caret>ll()
}
}
// TODO:
// SKIP_ANALYZE_CHECK
}
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
fun test() {
fun hello() {
<caret>
@@ -0,0 +1,8 @@
// OUT_OF_CODE_BLOCK: FALSE
class LocalFunWithBodyInClass {
fun test() {
fun hello() {
<caret>
}
}
}
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
object Some {
fun test() {
<caret>
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
object Some {
fun test() {
@@ -1,12 +1,9 @@
// TRUE
// OUT_OF_CODE_BLOCK: FALSE
// Problem with lazy initialization of nullable properties
interface Some
val test: Some = object: Some {
fun test() {
<caret>
}
}
// SKIP_ANALYZE_CHECK
}
@@ -1,6 +1,4 @@
// TRUE
// Problem with lazy initialization of nullable properties
// OUT_OF_CODE_BLOCK: FALSE
val test: Int? = if (true) {
fun test() {
@@ -9,6 +7,4 @@ val test: Int? = if (true) {
}
else {
}
// SKIP_ANALYZE_CHECK
}
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: FALSE
// Problem with lazy initialization of nullable properties
interface Some
@@ -7,6 +7,4 @@ val test: Some? = object: Some {
fun test() {
<caret>
}
}
// SKIP_ANALYZE_CHECK
}
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
// TODO: Investigate
@@ -1,5 +1,3 @@
// TRUE
// OUT_OF_CODE_BLOCK: FALSE
val test: String = "<caret>"
// SKIP_ANALYZE_CHECK
val test: String = "<caret>"
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
abstract class S(val f: () -> Unit)
+1 -1
View File
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
// SKIP_ANALYZE_CHECK
fun test() {<caret>
@@ -1,4 +1,4 @@
// FALSE
// OUT_OF_CODE_BLOCK: FALSE
plugins {
<caret>
}
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
fun foo() = 1
@@ -1,4 +1,4 @@
// TRUE
// OUT_OF_CODE_BLOCK: TRUE
1<caret>
// SKIP_ANALYZE_CHECK
@@ -26,11 +26,15 @@ import java.io.IOException;
public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCodeInsightFixtureTestCase {
public static final String OUT_OF_CODE_BLOCK_DIRECTIVE = "OUT_OF_CODE_BLOCK:";
public static final String SKIP_ANALYZE_CHECK_DIRECTIVE = "SKIP_ANALYZE_CHECK";
public static final String TYPE_DIRECTIVE = "TYPE:";
protected void doTest(String unused) throws IOException {
myFixture.configureByFile(fileName());
boolean expectedOutOfBlock = getExpectedOutOfBlockResult();
boolean isSkipCheckDefined = InTextDirectivesUtils.isDirectiveDefined(myFixture.getFile().getText(), "SKIP_ANALYZE_CHECK");
boolean isSkipCheckDefined = InTextDirectivesUtils.isDirectiveDefined(myFixture.getFile().getText(), SKIP_ANALYZE_CHECK_DIRECTIVE);
assertTrue("It's allowed to skip check with analyze only for tests where out-of-block is expected",
!isSkipCheckDefined || expectedOutOfBlock);
@@ -57,7 +61,7 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
assertEquals("Result for out of block test is differs from expected on element in file:\n"
+ FileUtil.loadFile(testDataFile()),
expectedOutOfBlock, oobBeforeType != oobAfterCount);
expectedOutOfBlock, oobBeforeType != oobAfterCount);
if (!isSkipCheckDefined) {
checkOOBWithDescriptorsResolve(expectedOutOfBlock);
@@ -65,13 +69,9 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
}
private void checkOOBWithDescriptorsResolve(boolean expectedOutOfBlock) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
((PsiModificationTrackerImpl) PsiManager.getInstance(myFixture.getProject()).getModificationTracker())
.incOutOfCodeBlockModificationCounter();
}
});
ApplicationManager.getApplication().runReadAction(
() -> ((PsiModificationTrackerImpl) PsiManager.getInstance(myFixture.getProject()).getModificationTracker())
.incOutOfCodeBlockModificationCounter());
PsiElement updateElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset() - 1);
KtExpression ktExpression = PsiTreeUtil.getParentOfType(updateElement, KtExpression.class, false);
@@ -104,7 +104,7 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
private String getStringToType() {
String text = myFixture.getDocument(myFixture.getFile()).getText();
String typeDirectives = InTextDirectivesUtils.findStringWithPrefixes(text, "TYPE:");
String typeDirectives = InTextDirectivesUtils.findStringWithPrefixes(text, TYPE_DIRECTIVE);
return typeDirectives != null ? StringUtil.unescapeStringCharacters(typeDirectives) : "a";
}
@@ -112,17 +112,12 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
private boolean getExpectedOutOfBlockResult() {
String text = myFixture.getDocument(myFixture.getFile()).getText();
boolean expectedOutOfBlock = false;
if (text.startsWith("// TRUE")) {
expectedOutOfBlock = true;
}
else if (text.startsWith("// FALSE")) {
expectedOutOfBlock = false;
}
else {
fail("Expectation of code block result test should be configured with " +
"\"// TRUE\" or \"// FALSE\" directive in the beginning of the file");
}
return expectedOutOfBlock;
String outOfCodeBlockDirective = InTextDirectivesUtils.findStringWithPrefixes(text, OUT_OF_CODE_BLOCK_DIRECTIVE);
assertNotNull(fileName() +
": Expectation of code block result test should be configured with " +
"\"// " + OUT_OF_CODE_BLOCK_DIRECTIVE + " TRUE\" or " +
"\"// " + OUT_OF_CODE_BLOCK_DIRECTIVE + " FALSE\" directive in the file",
outOfCodeBlockDirective);
return Boolean.parseBoolean(outOfCodeBlockDirective);
}
}
@@ -28,14 +28,9 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/outOfBlock"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
}
@TestMetadata("Class_Class_FunNoType_Block.kt")
public void testClass_Class_FunNoType_Block() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/Class_Class_FunNoType_Block.kt");
}
@TestMetadata("Class_Class_FunNoType_Block_Expression.kt")
public void testClass_Class_FunNoType_Block_Expression() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/Class_Class_FunNoType_Block_Expression.kt");
@TestMetadata("FunBlock.kt")
public void testFunBlock() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/FunBlock.kt");
}
@TestMetadata("FunInFun.kt")
@@ -83,9 +78,9 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InClass.kt");
}
@TestMetadata("InClassInClass.kt")
public void testInClassInClass() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InClassInClass.kt");
@TestMetadata("InClassFunctionWithoutInference.kt")
public void testInClassFunctionWithoutInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InClassFunctionWithoutInference.kt");
}
@TestMetadata("InClassPropertyAccessor.kt")
@@ -93,9 +88,19 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InClassPropertyAccessor.kt");
}
@TestMetadata("InFunInFunWithBody.kt")
public void testInFunInFunWithBody() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InFunInFunWithBody.kt");
@TestMetadata("InExtensionFunction.kt")
public void testInExtensionFunction() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InExtensionFunction.kt");
}
@TestMetadata("InExtensionFunctionWithInference.kt")
public void testInExtensionFunctionWithInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InExtensionFunctionWithInference.kt");
}
@TestMetadata("InExtensionFunctionWithoutInference.kt")
public void testInExtensionFunctionWithoutInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InExtensionFunctionWithoutInference.kt");
}
@TestMetadata("InFunInFunctionInitializerInFun.kt")
@@ -123,11 +128,6 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InFunObjectLiteral.kt");
}
@TestMetadata("InFunWithInference.kt")
public void testInFunWithInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InFunWithInference.kt");
}
@TestMetadata("InFunctionLiteral.kt")
public void testInFunctionLiteral() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InFunctionLiteral.kt");
@@ -138,11 +138,41 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InGlobalPropertyWithGetter.kt");
}
@TestMetadata("InLambdaFunction.kt")
public void testInLambdaFunction() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InLambdaFunction.kt");
}
@TestMetadata("InMethod.kt")
public void testInMethod() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InMethod.kt");
}
@TestMetadata("InNestedClass.kt")
public void testInNestedClass() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InNestedClass.kt");
}
@TestMetadata("InNestedClassFunNoTypeBlock.kt")
public void testInNestedClassFunNoTypeBlock() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InNestedClassFunNoTypeBlock.kt");
}
@TestMetadata("InNestedClassFunNoTypeBlockExpression.kt")
public void testInNestedClassFunNoTypeBlockExpression() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InNestedClassFunNoTypeBlockExpression.kt");
}
@TestMetadata("InPropertyAccessorSpecifyType.kt")
public void testInPropertyAccessorSpecifyType() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorSpecifyType.kt");
}
@TestMetadata("InPropertyAccessorWithAnnotation.kt")
public void testInPropertyAccessorWithAnnotation() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithAnnotation.kt");
}
@TestMetadata("InPropertyAccessorWithInference.kt")
public void testInPropertyAccessorWithInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithInference.kt");
@@ -153,6 +183,11 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithInferenceInClass.kt");
}
@TestMetadata("InPropertyAccessorWithoutInferenceInClass.kt")
public void testInPropertyAccessorWithoutInferenceInClass() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithoutInferenceInClass.kt");
}
@TestMetadata("InPropertyWithFunctionLiteral.kt")
public void testInPropertyWithFunctionLiteral() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyWithFunctionLiteral.kt");
@@ -178,11 +213,26 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InSuperTypeCallInLambdaParameters.kt");
}
@TestMetadata("InTopFun.kt")
public void testInTopFun() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InTopFun.kt");
}
@TestMetadata("InitBlock.kt")
public void testInitBlock() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InitBlock.kt");
}
@TestMetadata("LocalFunWithBody.kt")
public void testLocalFunWithBody() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/LocalFunWithBody.kt");
}
@TestMetadata("LocalFunWithBodyInClass.kt")
public void testLocalFunWithBodyInClass() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/LocalFunWithBodyInClass.kt");
}
@TestMetadata("Object_FunNoType_Block.kt")
public void testObject_FunNoType_Block() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/Object_FunNoType_Block.kt");
@@ -18,12 +18,17 @@ import java.util.List;
public abstract class AbstractHighlightingTest extends KotlinLightCodeInsightFixtureTestCase {
public static final String NO_CHECK_INFOS_PREFIX = "// NO_CHECK_INFOS";
public static final String NO_CHECK_WEAK_WARNINGS_PREFIX = "// NO_CHECK_WEAK_WARNINGS";
public static final String NO_CHECK_WARNINGS_PREFIX = "// NO_CHECK_WARNINGS";
public static final String EXPECTED_DUPLICATED_HIGHLIGHTING_PREFIX = "// EXPECTED_DUPLICATED_HIGHLIGHTING";
protected void doTest(String unused) throws Exception {
String fileText = FileUtil.loadFile(new File(testPath()), true);
boolean checkInfos = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_CHECK_INFOS");
boolean checkWeakWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_CHECK_WEAK_WARNINGS");
boolean checkWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_CHECK_WARNINGS");
boolean expectedDuplicatedHighlighting = InTextDirectivesUtils.isDirectiveDefined(fileText, "// EXPECTED_DUPLICATED_HIGHLIGHTING");
boolean checkInfos = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_INFOS_PREFIX);
boolean checkWeakWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_WEAK_WARNINGS_PREFIX);
boolean checkWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_WARNINGS_PREFIX);
boolean expectedDuplicatedHighlighting = InTextDirectivesUtils.isDirectiveDefined(fileText, EXPECTED_DUPLICATED_HIGHLIGHTING_PREFIX);
myFixture.configureByFile(fileName());