Provide incremental analysis of a file when it is applicable
#KT32868 Fixed
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-14
@@ -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"
|
||||||
|
|
||||||
|
|||||||
+222
-34
@@ -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),
|
||||||
|
|||||||
+3
-2
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
+117
-19
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-9
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -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) }
|
||||||
|
|
||||||
|
|||||||
+13
-14
@@ -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,
|
||||||
|
|||||||
+10
-1
@@ -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')
|
||||||
|
|||||||
+98
-79
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-15
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// OUT_OF_CODE_BLOCK: FALSE
|
||||||
|
fun some(): Int = run { 12 + <caret> }
|
||||||
|
|
||||||
|
// TYPE: 1
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// FALSE
|
// OUT_OF_CODE_BLOCK: FALSE
|
||||||
fun main() {
|
fun main() {
|
||||||
fun some = 12<caret>
|
fun some = 12<caret>
|
||||||
}
|
}
|
||||||
+2
-5
@@ -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
@@ -1,2 +1,2 @@
|
|||||||
// TRUE
|
// OUT_OF_CODE_BLOCK: TRUE
|
||||||
fun some() = <caret>12
|
fun some() = <caret>12
|
||||||
+1
-1
@@ -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
@@ -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
@@ -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,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,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
@@ -1,4 +1,4 @@
|
|||||||
// FALSE
|
// OUT_OF_CODE_BLOCK: FALSE
|
||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
val a = 1<caret>
|
val a = 1<caret>
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// TRUE
|
// OUT_OF_CODE_BLOCK: TRUE
|
||||||
class Test {
|
class Test {
|
||||||
class Other {
|
class Other {
|
||||||
<caret>
|
<caret>
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// FALSE
|
// OUT_OF_CODE_BLOCK: FALSE
|
||||||
|
|
||||||
class Test {
|
class Test {
|
||||||
class Other {
|
class Other {
|
||||||
+1
-1
@@ -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
-1
@@ -1,4 +1,4 @@
|
|||||||
// TRUE
|
// OUT_OF_CODE_BLOCK: TRUE
|
||||||
|
|
||||||
class A {
|
class A {
|
||||||
fun foo(): Int = 12
|
fun foo(): Int = 12
|
||||||
|
|||||||
+10
@@ -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("") })
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// OUT_OF_CODE_BLOCK: FALSE
|
||||||
|
fun more() { println<caret> }
|
||||||
+2
-5
@@ -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
-1
@@ -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() {
|
||||||
|
|||||||
+2
-5
@@ -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
|
|
||||||
+2
-4
@@ -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
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -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,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
|
||||||
|
|||||||
+17
-22
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-19
@@ -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());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user