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 allowSliceRewrite: Boolean = false
) : BindingTrace { ) : BindingTrace {
private val map = if (BindingTraceContext.TRACK_REWRITES && !allowSliceRewrite) protected val map = if (BindingTraceContext.TRACK_REWRITES && !allowSliceRewrite)
TrackingSlicedMap(BindingTraceContext.TRACK_WITH_STACK_TRACES) TrackingSlicedMap(BindingTraceContext.TRACK_WITH_STACK_TRACES)
else else
SlicedMapImpl(allowSliceRewrite) SlicedMapImpl(allowSliceRewrite)
private val mutableDiagnostics: MutableDiagnosticsWithSuppression? protected val mutableDiagnostics: MutableDiagnosticsWithSuppression?
private inner class MyBindingContext : BindingContext { private inner class MyBindingContext : BindingContext {
override fun getDiagnostics(): Diagnostics = mutableDiagnostics ?: Diagnostics.EMPTY override fun getDiagnostics(): Diagnostics = mutableDiagnostics ?: Diagnostics.EMPTY
@@ -99,16 +99,15 @@ open class DelegatingBindingTrace(
record(slice, key, true) record(slice, key, true)
} }
override fun <K, V> get(slice: ReadOnlySlice<K, V>, key: K): V? { override fun <K, V> get(slice: ReadOnlySlice<K, V>, key: K): V? =
val value = map.get(slice, key) selfGet(slice, key) ?: parentContext.get(slice, key)
if (slice is SetSlice<*>) {
assert(value != null)
if (value != SetSlice.DEFAULT) return value
} else if (value != null) {
return value
}
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> { 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) BindingContextUtils.addOwnDataTo(trace, filter, commitDiagnostics, map, mutableDiagnostics)
} }
fun clear() { open fun clear() {
map.clear() map.clear()
mutableDiagnostics?.clear() mutableDiagnostics?.clear()
} }
@@ -246,24 +246,15 @@ class KotlinCacheServiceImpl(val project: Project) : KotlinCacheService {
val specialModuleInfo = files.map(KtFile::getModuleInfo).toSet().single() val specialModuleInfo = files.map(KtFile::getModuleInfo).toSet().single()
val settings = specialModuleInfo.platformSettings(specialModuleInfo.platform ?: targetPlatform) 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. // Dummy files created e.g. by J2K do not receive events.
val filesModificationTracker = if (files.all { it.originalFile != it }) { val dependenciesForSyntheticFileCache = if (files.all { it.originalFile != it }) {
ModificationTracker { emptyList()
files.sumByLong { it.outOfBlockModificationCount }
}
} else { } else {
ModificationTracker { listOf(ModificationTracker {
files.sumByLong { it.outOfBlockModificationCount + it.modificationStamp } files.sumByLong { it.modificationStamp }
} })
} }
val dependenciesForSyntheticFileCache =
listOf(
KotlinCodeBlockModificationListener.getInstance(project).kotlinOutOfCodeBlockTracker,
filesModificationTracker
)
val resolverDebugName = val resolverDebugName =
"$resolverForSpecialInfoName $specialModuleInfo for files ${files.joinToString { it.name }} for platform $targetPlatform" "$resolverForSpecialInfoName $specialModuleInfo for files ${files.joinToString { it.name }} for platform $targetPlatform"
@@ -16,12 +16,15 @@
package org.jetbrains.kotlin.idea.caches.resolve package org.jetbrains.kotlin.idea.caches.resolve
import com.google.common.collect.ImmutableMap
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.ModificationTracker
import com.intellij.psi.PsiElement 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.analyzer.AnalysisResult
import org.jetbrains.kotlin.container.ComponentProvider import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get 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.withModule
import org.jetbrains.kotlin.context.withProject import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve import org.jetbrains.kotlin.frontend.di.createContainerForLazyBodyResolve
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo 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.IdeaModuleStructureOracle
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.project.findAnalyzerServices import org.jetbrains.kotlin.idea.project.findAnalyzerServices
import org.jetbrains.kotlin.idea.project.languageVersionSettings import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.* 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.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.ResolveSession 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.* import java.util.*
internal class PerFileAnalysisCache(val file: KtFile, componentProvider: ComponentProvider) { 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 bodyResolveCache = componentProvider.get<BodyResolveCache>()
private val cache = HashMap<PsiElement, AnalysisResult>() 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? { private fun lookUp(analyzableElement: KtElement): AnalysisResult? {
// Looking for parent elements that are already analyzed // Looking for parent elements that are already analyzed
@@ -75,25 +168,26 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
return result return result
} }
fun getAnalysisResults(element: KtElement): AnalysisResult { private fun wrapResult(
assert(element.containingKtFile == file) { "Wrong file. Expected $file, but was ${element.containingKtFile}" } oldResult: AnalysisResult,
newResult: AnalysisResult,
val analyzableParent = KotlinResolveDataProvider.findAnalyzableParent(element) elementBindingTrace: StackedCompositeBindingContextTrace
): AnalysisResult {
return synchronized<AnalysisResult>(this) { val newBindingCtx = elementBindingTrace.stackedContext
return when {
val cached = lookUp(analyzableParent) oldResult.isError() -> AnalysisResult.internalError(newBindingCtx, oldResult.error)
if (cached != null) return@synchronized cached newResult.isError() -> AnalysisResult.internalError(newBindingCtx, newResult.error)
else -> AnalysisResult.success(
val result = analyze(analyzableParent) newBindingCtx,
oldResult.moduleDescriptor,
cache[analyzableParent] = result oldResult.shouldGenerateCode
)
return@synchronized result
} }
} }
private fun analyze(analyzableElement: KtElement): AnalysisResult { private fun analyze(analyzableElement: KtElement, bindingTrace: BindingTrace? = null): AnalysisResult {
ProgressIndicatorProvider.checkCanceled()
val project = analyzableElement.project val project = analyzableElement.project
if (DumbService.isDumb(project)) { if (DumbService.isDumb(project)) {
return AnalysisResult.EMPTY return AnalysisResult.EMPTY
@@ -107,7 +201,8 @@ internal class PerFileAnalysisCache(val file: KtFile, componentProvider: Compone
resolveSession, resolveSession,
codeFragmentAnalyzer, codeFragmentAnalyzer,
bodyResolveCache, bodyResolveCache,
analyzableElement analyzableElement,
bindingTrace
) )
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
throw e 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 object KotlinResolveDataProvider {
private val topmostElementTypes = arrayOf<Class<out PsiElement?>?>( private val topmostElementTypes = arrayOf<Class<out PsiElement?>?>(
KtNamedFunction::class.java, KtNamedFunction::class.java,
@@ -159,9 +347,9 @@ private object KotlinResolveDataProvider {
if (analyzableElement is KtClassInitializer) return analyzableElement.containingDeclaration if (analyzableElement is KtClassInitializer) return analyzableElement.containingDeclaration
return analyzableElement return analyzableElement
// if none of the above worked, take the outermost declaration // if none of the above worked, take the outermost declaration
?: PsiTreeUtil.getTopmostParentOfType(element, KtDeclaration::class.java) ?: PsiTreeUtil.getTopmostParentOfType(element, KtDeclaration::class.java)
// if even that didn't work, take the whole file // if even that didn't work, take the whole file
?: element.containingKtFile ?: element.containingKtFile
} }
fun analyze( fun analyze(
@@ -171,7 +359,8 @@ private object KotlinResolveDataProvider {
resolveSession: ResolveSession, resolveSession: ResolveSession,
codeFragmentAnalyzer: CodeFragmentAnalyzer, codeFragmentAnalyzer: CodeFragmentAnalyzer,
bodyResolveCache: BodyResolveCache, bodyResolveCache: BodyResolveCache,
analyzableElement: KtElement analyzableElement: KtElement,
bindingTrace: BindingTrace?
): AnalysisResult { ): AnalysisResult {
try { try {
if (analyzableElement is KtCodeFragment) { if (analyzableElement is KtCodeFragment) {
@@ -180,6 +369,16 @@ private object KotlinResolveDataProvider {
return AnalysisResult.success(bindingContext, moduleDescriptor) 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 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). 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 (see 'functionAdditionalResolve'). However, this trace is still needed, because we have other
codepaths for other KtDeclarationWithBodies (like property accessors/secondary constructors/class initializers) 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( val lazyTopDownAnalyzer = createContainerForLazyBodyResolve(
//TODO: should get ModuleContext //TODO: should get ModuleContext
globalContext.withProject(project).withModule(moduleDescriptor), globalContext.withProject(project).withModule(moduleDescriptor),
@@ -20,7 +20,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.SLRUCache import com.intellij.util.containers.SLRUCache
import org.jetbrains.kotlin.analyzer.* import org.jetbrains.kotlin.analyzer.*
import org.jetbrains.kotlin.context.GlobalContextImpl 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.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.caches.project.* import org.jetbrains.kotlin.idea.caches.project.*
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo 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.KtElement
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompositeBindingContext 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) CachedValueProvider.Result.create(results, allDependencies)
}, false }, false
) )
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.caches.trackers package org.jetbrains.kotlin.idea.caches.trackers
import com.intellij.lang.ASTNode
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.ModificationTracker 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.event.PomModelListener
import com.intellij.pom.tree.TreeAspect import com.intellij.pom.tree.TreeAspect
import com.intellij.pom.tree.events.TreeChangeEvent import com.intellij.pom.tree.events.TreeChangeEvent
import com.intellij.psi.PsiDirectory import com.intellij.psi.*
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiTreeChangeEvent
import com.intellij.psi.impl.PsiManagerImpl import com.intellij.psi.impl.PsiManagerImpl
import com.intellij.psi.impl.PsiModificationTrackerImpl import com.intellij.psi.impl.PsiModificationTrackerImpl
import com.intellij.psi.impl.PsiTreeChangeEventImpl import com.intellij.psi.impl.PsiTreeChangeEventImpl
@@ -89,9 +87,17 @@ class KotlinCodeBlockModificationListener(
incFileModificationCount(ktFile) incFileModificationCount(ktFile)
val changedElements = changeSet.changedElements 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 // skip change if it contains only virtual/fake change
if (changedElements.any { getInsideCodeBlockModificationScope(it.psi) == null } || changedElements.isEmpty()) { if (changedElements.isNotEmpty() &&
// ignore formatting (whitespaces etc)
(isFormattingChange(changeSet) ||
changedElements.all { !it.psi.isPhysical })
) return
val inBlockChange = inBlockModifications(changedElements)
if (!inBlockChange) {
messageBusConnection.deliverImmediately() messageBusConnection.deliverImmediately()
if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) { if (ktFile.isPhysical && !isReplLine(ktFile.virtualFile)) {
@@ -155,6 +161,8 @@ class KotlinCodeBlockModificationListener(
} }
private fun incOutOfBlockModificationCount(file: KtFile) { private fun incOutOfBlockModificationCount(file: KtFile) {
file.clearInBlockModifications()
val count = file.getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0 val count = file.getUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT) ?: 0
file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1) file.putUserData(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, count + 1)
} }
@@ -165,32 +173,72 @@ class KotlinCodeBlockModificationListener(
tracker.incModificationCount() 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>() val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
if (lambda is KtLambdaExpression) { if (lambda is KtLambdaExpression) {
lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.let { lambda.getTopmostParentOfType<KtSuperTypeCallEntry>()?.let {
return it return BlockModificationScopeElement(it, it)
} }
} }
val blockDeclaration = KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null val blockDeclaration =
if (KtPsiUtil.isLocal(blockDeclaration)) return null // should not be local declaration KtPsiUtil.getTopmostParentOfTypes(element, *BLOCK_DECLARATION_TYPES) as? KtDeclaration ?: return null
// should not be local declaration
if (KtPsiUtil.isLocal(blockDeclaration))
return null
when (blockDeclaration) { when (blockDeclaration) {
is KtNamedFunction -> { is KtNamedFunction -> {
if (blockDeclaration.hasBlockBody()) { 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()) { } 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 -> { is KtProperty -> {
if (blockDeclaration.typeReference != null) { if (blockDeclaration.typeReference != null) {
for (accessor in blockDeclaration.accessors) { val accessors =
(accessor.initializer ?: accessor.bodyExpression) blockDeclaration.accessors.map { it.initializer ?: it.bodyExpression } + blockDeclaration.initializer
?.takeIf { it.isAncestor(element) } for (accessor in accessors) {
?.let { return it } 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() ?.lastOrNull()
?.getLambdaExpression() ?.getLambdaExpression()
?.takeIf { it.isAncestor(element) } ?.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() else -> throw IllegalStateException()
} }
return null return null
} }
data class BlockModificationScopeElement(val blockDeclaration: KtElement, val element: KtElement)
fun isBlockDeclaration(declaration: KtDeclaration): Boolean { fun isBlockDeclaration(declaration: KtDeclaration): Boolean {
return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) } return BLOCK_DECLARATION_TYPES.any { it.isInstance(declaration) }
} }
@@ -216,6 +287,7 @@ class KotlinCodeBlockModificationListener(
private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>( private val BLOCK_DECLARATION_TYPES = arrayOf<Class<out KtDeclaration>>(
KtProperty::class.java, KtProperty::class.java,
KtNamedFunction::class.java, KtNamedFunction::class.java,
KtClassInitializer::class.java,
KtScriptInitializer::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") private val FILE_OUT_OF_BLOCK_MODIFICATION_COUNT = Key<Long>("FILE_OUT_OF_BLOCK_MODIFICATION_COUNT")
val KtFile.outOfBlockModificationCount: Long val KtFile.outOfBlockModificationCount: Long by NotNullableUserDataProperty(FILE_OUT_OF_BLOCK_MODIFICATION_COUNT, 0)
get() = getUserData(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.codeInsight.daemon.ChangeLocalityDetector
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener.Companion.getInsideCodeBlockModificationScope
import org.jetbrains.kotlin.psi.psiUtil.parents
class KotlinChangeLocalityDetector : ChangeLocalityDetector { class KotlinChangeLocalityDetector : ChangeLocalityDetector {
override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? { override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? {
val parent = element.parent val modificationScope =
if (element is KtBlockExpression && parent is KtNamedFunction && parent.name != null) { getInsideCodeBlockModificationScope(element) ?: return null
if (parent.parents.all { it is KtClassBody || it is KtClassOrObject || it is KtFile || it is KtScript }) {
return parent
}
}
return null return modificationScope.blockDeclaration
} }
} }
@@ -107,7 +107,7 @@ class CompletionBindingContextProvider(project: Project) {
val inStatement = position.findStatementInBlock() val inStatement = position.findStatementInBlock()
val block = inStatement?.parent as KtBlockExpression? val block = inStatement?.parent as KtBlockExpression?
val prevStatement = inStatement?.siblings(forward = false, withItself = false)?.firstIsInstanceOrNull<KtExpression>() 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) } val psiElementsBeforeAndAfter = modificationScope?.let { collectPsiElementsBeforeAndAfter(modificationScope, inStatement) }
@@ -19,13 +19,16 @@ import org.jetbrains.kotlin.utils.addToStdlib.indexOfOrNull
import java.io.File import java.io.File
abstract class AbstractCompletionHandlerTest(private val defaultCompletionType: CompletionType) : CompletionHandlerTestBase() { abstract class AbstractCompletionHandlerTest(private val defaultCompletionType: CompletionType) : CompletionHandlerTestBase() {
private val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
private val LOOKUP_STRING_PREFIX = "ELEMENT:" companion object {
private val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:" val INVOCATION_COUNT_PREFIX = "INVOCATION_COUNT:"
private val TAIL_TEXT_PREFIX = "TAIL_TEXT:" val LOOKUP_STRING_PREFIX = "ELEMENT:"
private val COMPLETION_CHAR_PREFIX = "CHAR:" val ELEMENT_TEXT_PREFIX = "ELEMENT_TEXT:"
private val COMPLETION_CHARS_PREFIX = "CHARS:" val TAIL_TEXT_PREFIX = "TAIL_TEXT:"
private val CODE_STYLE_SETTING_PREFIX = "CODE_STYLE_SETTING:" val COMPLETION_CHAR_PREFIX = "CHAR:"
val COMPLETION_CHARS_PREFIX = "CHARS:"
val CODE_STYLE_SETTING_PREFIX = "CODE_STYLE_SETTING:"
}
protected open fun doTest(testPath: String) { protected open fun doTest(testPath: String) {
setUpFixture(fileName()) setUpFixture(fileName())
@@ -42,11 +45,7 @@ abstract class AbstractCompletionHandlerTest(private val defaultCompletionType:
val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX) val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX)
val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX) val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX)
val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX) val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX)
val completionChars = completionChars(fileText)
val completionChars = completionChars(
char = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX),
chars = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHARS_PREFIX)
)
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
@@ -58,8 +57,7 @@ abstract class AbstractCompletionHandlerTest(private val defaultCompletionType:
val settingValue = line.substring(index + 1).trim() val settingValue = line.substring(index + 1).trim()
val (field, settings) = try { val (field, settings) = try {
kotlinStyleSettings::class.java.getField(settingName) to kotlinStyleSettings kotlinStyleSettings::class.java.getField(settingName) to kotlinStyleSettings
} } catch (e: NoSuchFieldException) {
catch (e: NoSuchFieldException) {
commonStyleSettings::class.java.getField(settingName) to commonStyleSettings commonStyleSettings::class.java.getField(settingName) to commonStyleSettings
} }
when (field.type.name) { when (field.type.name) {
@@ -70,6 +68,7 @@ abstract class AbstractCompletionHandlerTest(private val defaultCompletionType:
} }
doTestWithTextLoaded( doTestWithTextLoaded(
myFixture,
completionType, completionType,
invocationCount, invocationCount,
lookupString, lookupString,
@@ -31,7 +31,16 @@ class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
private fun doTest(time: Int, lookupString: String?, itemText: String?, tailText: String?, completionChar: Char) { private fun doTest(time: Int, lookupString: String?, itemText: String?, tailText: String?, completionChar: Char) {
fixture.configureByFile(fileName()) 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') 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.LookupEvent
import com.intellij.codeInsight.lookup.LookupManager import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.impl.LookupImpl import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.openapi.project.Project
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.completion.test.ExpectedCompletionUtils import org.jetbrains.kotlin.idea.completion.test.ExpectedCompletionUtils
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.test.InTextDirectivesUtils
abstract class CompletionHandlerTestBase : KotlinLightCodeInsightFixtureTestCase() { abstract class CompletionHandlerTestBase : KotlinLightCodeInsightFixtureTestCase() {
protected val fixture: JavaCodeInsightTestFixture protected val fixture: JavaCodeInsightTestFixture
get() = myFixture get() = myFixture
protected fun doTestWithTextLoaded( companion object {
completionType: CompletionType, fun doTestWithTextLoaded(
time: Int, fixture: JavaCodeInsightTestFixture,
lookupString: String?, completionType: CompletionType,
itemText: String?, time: Int,
tailText: String?, lookupString: String?,
completionChars: String, itemText: String?,
afterFilePath: String tailText: String?,
) { completionChars: String,
for (idx in 0 until completionChars.length - 1) { afterFilePath: String,
fixture.type(completionChars[idx]) 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) fun completionChars(text: String): String {
val char: String? = InTextDirectivesUtils.findStringWithPrefixes(text, AbstractCompletionHandlerTest.COMPLETION_CHAR_PREFIX)
if (lookupString != null || itemText != null || tailText != null) { val chars: String? = InTextDirectivesUtils.findStringWithPrefixes(text, AbstractCompletionHandlerTest.COMPLETION_CHARS_PREFIX)
val item = getExistentLookupElement(lookupString, itemText, tailText) return when (char) {
if (item != null) { null -> when (chars) {
selectItem(item, completionChars.last()) 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 = fun getExistentLookupElement(project: Project, lookupString: String?, itemText: String?, tailText: String?): LookupElement? {
when (char) { val lookup = LookupManager.getInstance(project)?.activeLookup as LookupImpl? ?: return null
null -> when (chars) { val items = lookup.items
null -> "\n"
else -> chars.replace("\\n", "\n").replace("\\t", "\t") 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? { var foundElement : LookupElement? = null
val lookup = LookupManager.getInstance(project)?.activeLookup as LookupImpl? ?: return null val presentation = LookupElementPresentation()
val items = lookup.items for (lookupElement in items) {
val lookupOk = if (lookupString != null) lookupElement.lookupString == lookupString else true
if (lookupString == "*") { if (lookupOk) {
assert(itemText == null) lookupElement.renderElement(presentation)
assert(tailText == null)
return items.firstOrNull()
}
var foundElement : LookupElement? = null val textOk = if (itemText != null) {
val presentation = LookupElementPresentation() val itemItemText = presentation.itemText
for (lookupElement in items) { itemItemText != null && itemItemText == itemText
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
} }
else { else {
true true
} }
if (tailOk) { if (textOk) {
if (foundElement != null) { val tailOk = if (tailText != null) {
val dump = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(arrayOf(foundElement, lookupElement))) val itemTailText = presentation.tailText
fail("Several elements satisfy to completion restrictions:\n$dump") 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) { fun selectItem(fixture: JavaCodeInsightTestFixture, item: LookupElement?, completionChar: Char) {
val dump = ExpectedCompletionUtils.listToString(ExpectedCompletionUtils.getItemsInformation(items.toTypedArray())) val lookup = (fixture.lookup as LookupImpl)
error("No element satisfy completion restrictions in:\n$dump") if (lookup.currentItem != item) { // do not touch selection if not changed - important for char filter tests
} lookup.currentItem = item
return foundElement }
} lookup.focusDegree = LookupImpl.FocusDegree.FOCUSED
if (LookupEvent.isSpecialCompletionChar(completionChar)) {
protected fun selectItem(item: LookupElement?, completionChar: Char) { lookup.finishLookup(completionChar)
val lookup = (fixture.lookup as LookupImpl) }
if (lookup.currentItem != item) { // do not touch selection if not changed - important for char filter tests else {
lookup.currentItem = item fixture.type(completionChar)
} }
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 com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.idea.completion.test.ExpectedCompletionUtils import org.jetbrains.kotlin.idea.completion.test.ExpectedCompletionUtils
import org.jetbrains.kotlin.idea.completion.test.configureWithExtraFile 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.core.formatter.KotlinCodeStyleSettings
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.configureCompilerOptions import org.jetbrains.kotlin.idea.test.configureCompilerOptions
@@ -29,14 +36,6 @@ abstract class AbstractPerformanceCompletionHandlerTests(
private val note: String = "" private val note: String = ""
) : CompletionHandlerTestBase() { ) : 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 { companion object {
@JvmStatic @JvmStatic
val statsMap: MutableMap<String, Stats> = mutableMapOf() val statsMap: MutableMap<String, Stats> = mutableMapOf()
@@ -74,11 +73,7 @@ abstract class AbstractPerformanceCompletionHandlerTests(
val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX) val lookupString = InTextDirectivesUtils.findStringWithPrefixes(fileText, LOOKUP_STRING_PREFIX)
val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX) val itemText = InTextDirectivesUtils.findStringWithPrefixes(fileText, ELEMENT_TEXT_PREFIX)
val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX) val tailText = InTextDirectivesUtils.findStringWithPrefixes(fileText, TAIL_TEXT_PREFIX)
val completionChars = completionChars(fileText)
val completionChars = completionChars(
char = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHAR_PREFIX),
chars = InTextDirectivesUtils.findStringWithPrefixes(fileText, COMPLETION_CHARS_PREFIX)
)
val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType val completionType = ExpectedCompletionUtils.getCompletionType(fileText) ?: defaultCompletionType
@@ -157,9 +152,9 @@ abstract class AbstractPerformanceCompletionHandlerTests(
fixture.complete(completionType, time) fixture.complete(completionType, time)
if (lookupString != null || itemText != null || tailText != null) { if (lookupString != null || itemText != null || tailText != null) {
val item = getExistentLookupElement(lookupString, itemText, tailText) val item = getExistentLookupElement(project, lookupString, itemText, tailText)
if (item != null) { 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 main() {
fun some = 12<caret> fun some = 12<caret>
} }
+2 -5
View File
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: FALSE
class A { class A {
init { init {
@@ -6,7 +6,4 @@ class A {
<caret> <caret>
} }
} }
} }
// TODO
// SKIP_ANALYZE_CHECK
+1 -1
View File
@@ -1,2 +1,2 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
fun some() = <caret>12 fun some() = <caret>12
+1 -1
View File
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
fun test() {<caret> fun test() {<caret>
} }
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
fun test() { fun test() {
class Test { class Test {
<caret> <caret>
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
// (Investigation starts from parent) // (Investigation starts from parent)
fun test() : Int = <caret>12 fun test() : Int = <caret>12
@@ -1,3 +1,3 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
fun test() : Int = 12 + <caret>12 fun test() : Int = 12 + <caret>12
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
val o = object { val o = object {
fun test() { fun test() {
<caret> <caret>
+1 -1
View File
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
class Some { class Some {
fun <caret> 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 { class Test {
val more : Int = 0 val more : Int = 0
val test : Int 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 val a = 1
fun test() = if (a) { fun test() = if (a) {
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
data class Test(val a : Int, val b : Int) 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() { fun test() {
val some = if () { val some = if () {
fun other() { fun other() {
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
interface Some interface Some
fun test() { fun test() {
+1 -1
View File
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
interface Some interface Some
fun test() { 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() { fun main() {
jq() { pri<caret> } jq() { pri<caret> }
@@ -1,5 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
// Important for 173 branch! OOCB is TRUE in this test because of IDEA-185462
class B(val a: A) class B(val a: A)
val B.foo: Int 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() { fun test() {
val a = 1<caret> val a = 1<caret>
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
class Test { class Test {
class Other { class Other {
<caret> <caret>
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
class Test { class Test {
class Other { class Other {
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
class Test { class Test {
class Other { 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 val test : Int
get() = <caret>12 get() = <caret>12
// TODO // TODO: Investigate
// SKIP_ANALYZE_CHECK // SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
class A { class A {
fun foo(): Int = 12 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 { class Test {
val a : () -> Int = { <caret>pri } val a : () -> Int = { <caret>pri }
} }
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
class Test { class Test {
val a = "aasdf<caret>" 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 // 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) open class A(a: () -> Unit)
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
open class A(a: () -> Unit) { open class A(a: () -> Unit) {
constructor(f: (String) -> Unit) : this({ -> f("") }) 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 { class A {
init { init {
ca<caret>ll() ca<caret>ll()
} }
} }
// TODO:
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
fun test() { fun test() {
fun hello() { fun hello() {
<caret> <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 { object Some {
fun test() { fun test() {
<caret> <caret>
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
object Some { object Some {
fun test() { fun test() {
@@ -1,12 +1,9 @@
// TRUE // OUT_OF_CODE_BLOCK: FALSE
// Problem with lazy initialization of nullable properties
interface Some interface Some
val test: Some = object: Some { val test: Some = object: Some {
fun test() { fun test() {
<caret> <caret>
} }
} }
// SKIP_ANALYZE_CHECK
@@ -1,6 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: FALSE
// Problem with lazy initialization of nullable properties
val test: Int? = if (true) { val test: Int? = if (true) {
fun test() { fun test() {
@@ -9,6 +7,4 @@ val test: Int? = if (true) {
} }
else { else {
} }
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: FALSE
// Problem with lazy initialization of nullable properties // Problem with lazy initialization of nullable properties
interface Some interface Some
@@ -7,6 +7,4 @@ val test: Some? = object: Some {
fun test() { fun test() {
<caret> <caret>
} }
} }
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
// TODO: Investigate // TODO: Investigate
@@ -1,5 +1,3 @@
// TRUE // OUT_OF_CODE_BLOCK: FALSE
val test: String = "<caret>" val test: String = "<caret>"
// SKIP_ANALYZE_CHECK
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
abstract class S(val f: () -> Unit) abstract class S(val f: () -> Unit)
+1 -1
View File
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
// SKIP_ANALYZE_CHECK // SKIP_ANALYZE_CHECK
fun test() {<caret> fun test() {<caret>
@@ -1,4 +1,4 @@
// FALSE // OUT_OF_CODE_BLOCK: FALSE
plugins { plugins {
<caret> <caret>
} }
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
fun foo() = 1 fun foo() = 1
@@ -1,4 +1,4 @@
// TRUE // OUT_OF_CODE_BLOCK: TRUE
1<caret> 1<caret>
// SKIP_ANALYZE_CHECK // SKIP_ANALYZE_CHECK
@@ -26,11 +26,15 @@ import java.io.IOException;
public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCodeInsightFixtureTestCase { 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 { protected void doTest(String unused) throws IOException {
myFixture.configureByFile(fileName()); myFixture.configureByFile(fileName());
boolean expectedOutOfBlock = getExpectedOutOfBlockResult(); 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", assertTrue("It's allowed to skip check with analyze only for tests where out-of-block is expected",
!isSkipCheckDefined || expectedOutOfBlock); !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" assertEquals("Result for out of block test is differs from expected on element in file:\n"
+ FileUtil.loadFile(testDataFile()), + FileUtil.loadFile(testDataFile()),
expectedOutOfBlock, oobBeforeType != oobAfterCount); expectedOutOfBlock, oobBeforeType != oobAfterCount);
if (!isSkipCheckDefined) { if (!isSkipCheckDefined) {
checkOOBWithDescriptorsResolve(expectedOutOfBlock); checkOOBWithDescriptorsResolve(expectedOutOfBlock);
@@ -65,13 +69,9 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
} }
private void checkOOBWithDescriptorsResolve(boolean expectedOutOfBlock) { private void checkOOBWithDescriptorsResolve(boolean expectedOutOfBlock) {
ApplicationManager.getApplication().runReadAction(new Runnable() { ApplicationManager.getApplication().runReadAction(
@Override () -> ((PsiModificationTrackerImpl) PsiManager.getInstance(myFixture.getProject()).getModificationTracker())
public void run() { .incOutOfCodeBlockModificationCounter());
((PsiModificationTrackerImpl) PsiManager.getInstance(myFixture.getProject()).getModificationTracker())
.incOutOfCodeBlockModificationCounter();
}
});
PsiElement updateElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset() - 1); PsiElement updateElement = myFixture.getFile().findElementAt(myFixture.getCaretOffset() - 1);
KtExpression ktExpression = PsiTreeUtil.getParentOfType(updateElement, KtExpression.class, false); KtExpression ktExpression = PsiTreeUtil.getParentOfType(updateElement, KtExpression.class, false);
@@ -104,7 +104,7 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
private String getStringToType() { private String getStringToType() {
String text = myFixture.getDocument(myFixture.getFile()).getText(); 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"; return typeDirectives != null ? StringUtil.unescapeStringCharacters(typeDirectives) : "a";
} }
@@ -112,17 +112,12 @@ public abstract class AbstractOutOfBlockModificationTest extends KotlinLightCode
private boolean getExpectedOutOfBlockResult() { private boolean getExpectedOutOfBlockResult() {
String text = myFixture.getDocument(myFixture.getFile()).getText(); String text = myFixture.getDocument(myFixture.getFile()).getText();
boolean expectedOutOfBlock = false; String outOfCodeBlockDirective = InTextDirectivesUtils.findStringWithPrefixes(text, OUT_OF_CODE_BLOCK_DIRECTIVE);
if (text.startsWith("// TRUE")) { assertNotNull(fileName() +
expectedOutOfBlock = true; ": Expectation of code block result test should be configured with " +
} "\"// " + OUT_OF_CODE_BLOCK_DIRECTIVE + " TRUE\" or " +
else if (text.startsWith("// FALSE")) { "\"// " + OUT_OF_CODE_BLOCK_DIRECTIVE + " FALSE\" directive in the file",
expectedOutOfBlock = false; outOfCodeBlockDirective);
} return Boolean.parseBoolean(outOfCodeBlockDirective);
else {
fail("Expectation of code block result test should be configured with " +
"\"// TRUE\" or \"// FALSE\" directive in the beginning of the file");
}
return expectedOutOfBlock;
} }
} }
@@ -28,14 +28,9 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/outOfBlock"), Pattern.compile("^(.+)\\.(kt|kts)$"), true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/outOfBlock"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
} }
@TestMetadata("Class_Class_FunNoType_Block.kt") @TestMetadata("FunBlock.kt")
public void testClass_Class_FunNoType_Block() throws Exception { public void testFunBlock() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/Class_Class_FunNoType_Block.kt"); runTest("idea/testData/codeInsight/outOfBlock/FunBlock.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("FunInFun.kt") @TestMetadata("FunInFun.kt")
@@ -83,9 +78,9 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InClass.kt"); runTest("idea/testData/codeInsight/outOfBlock/InClass.kt");
} }
@TestMetadata("InClassInClass.kt") @TestMetadata("InClassFunctionWithoutInference.kt")
public void testInClassInClass() throws Exception { public void testInClassFunctionWithoutInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InClassInClass.kt"); runTest("idea/testData/codeInsight/outOfBlock/InClassFunctionWithoutInference.kt");
} }
@TestMetadata("InClassPropertyAccessor.kt") @TestMetadata("InClassPropertyAccessor.kt")
@@ -93,9 +88,19 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InClassPropertyAccessor.kt"); runTest("idea/testData/codeInsight/outOfBlock/InClassPropertyAccessor.kt");
} }
@TestMetadata("InFunInFunWithBody.kt") @TestMetadata("InExtensionFunction.kt")
public void testInFunInFunWithBody() throws Exception { public void testInExtensionFunction() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InFunInFunWithBody.kt"); 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") @TestMetadata("InFunInFunctionInitializerInFun.kt")
@@ -123,11 +128,6 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InFunObjectLiteral.kt"); 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") @TestMetadata("InFunctionLiteral.kt")
public void testInFunctionLiteral() throws Exception { public void testInFunctionLiteral() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InFunctionLiteral.kt"); runTest("idea/testData/codeInsight/outOfBlock/InFunctionLiteral.kt");
@@ -138,11 +138,41 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InGlobalPropertyWithGetter.kt"); 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") @TestMetadata("InMethod.kt")
public void testInMethod() throws Exception { public void testInMethod() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InMethod.kt"); 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") @TestMetadata("InPropertyAccessorWithInference.kt")
public void testInPropertyAccessorWithInference() throws Exception { public void testInPropertyAccessorWithInference() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithInference.kt"); runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithInference.kt");
@@ -153,6 +183,11 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InPropertyAccessorWithInferenceInClass.kt"); 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") @TestMetadata("InPropertyWithFunctionLiteral.kt")
public void testInPropertyWithFunctionLiteral() throws Exception { public void testInPropertyWithFunctionLiteral() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InPropertyWithFunctionLiteral.kt"); runTest("idea/testData/codeInsight/outOfBlock/InPropertyWithFunctionLiteral.kt");
@@ -178,11 +213,26 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("idea/testData/codeInsight/outOfBlock/InSuperTypeCallInLambdaParameters.kt"); 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") @TestMetadata("InitBlock.kt")
public void testInitBlock() throws Exception { public void testInitBlock() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/InitBlock.kt"); 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") @TestMetadata("Object_FunNoType_Block.kt")
public void testObject_FunNoType_Block() throws Exception { public void testObject_FunNoType_Block() throws Exception {
runTest("idea/testData/codeInsight/outOfBlock/Object_FunNoType_Block.kt"); runTest("idea/testData/codeInsight/outOfBlock/Object_FunNoType_Block.kt");
@@ -18,12 +18,17 @@ import java.util.List;
public abstract class AbstractHighlightingTest extends KotlinLightCodeInsightFixtureTestCase { 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 { protected void doTest(String unused) throws Exception {
String fileText = FileUtil.loadFile(new File(testPath()), true); String fileText = FileUtil.loadFile(new File(testPath()), true);
boolean checkInfos = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_CHECK_INFOS"); boolean checkInfos = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_INFOS_PREFIX);
boolean checkWeakWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_CHECK_WEAK_WARNINGS"); boolean checkWeakWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_WEAK_WARNINGS_PREFIX);
boolean checkWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, "// NO_CHECK_WARNINGS"); boolean checkWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_WARNINGS_PREFIX);
boolean expectedDuplicatedHighlighting = InTextDirectivesUtils.isDirectiveDefined(fileText, "// EXPECTED_DUPLICATED_HIGHLIGHTING"); boolean expectedDuplicatedHighlighting = InTextDirectivesUtils.isDirectiveDefined(fileText, EXPECTED_DUPLICATED_HIGHLIGHTING_PREFIX);
myFixture.configureByFile(fileName()); myFixture.configureByFile(fileName());