[kotlin] Fix incremental analysis for inner class bodies

#KTIJ-18388 Fixed

GitOrigin-RevId: 635fbea4ce7ed1cf6cc535aaca04c2c3331eb35f
This commit is contained in:
Vladimir Dolzhenko
2021-10-20 17:04:15 +00:00
committed by intellij-monorepo-bot
parent 3c27b879a4
commit f169366478
5 changed files with 106 additions and 10 deletions
@@ -20,7 +20,6 @@ import com.intellij.pom.tree.events.impl.ChangeInfoImpl
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.impl.source.tree.SharedImplUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.findTopmostParentInFile
import com.intellij.psi.util.findTopmostParentOfType
@@ -111,6 +110,19 @@ class PureKotlinCodeBlockModificationListener(project: Project) : Disposable {
if (KtPsiUtil.isLocal(blockDeclaration))
return null
val directParentClassOrObject = PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java)
val parentClassOrObject = directParentClassOrObject
?.takeIf { !it.isTopLevel() && it.hasModifier(KtTokens.INNER_KEYWORD) }?.let {
var e: KtClassOrObject? = it
while (e != null) {
e = PsiTreeUtil.getParentOfType(e, KtClassOrObject::class.java)
if (e?.hasModifier(KtTokens.INNER_KEYWORD) == false) {
break
}
}
e
} ?: directParentClassOrObject
when (blockDeclaration) {
is KtNamedFunction -> {
// if (blockDeclaration.visibilityModifierType()?.toVisibility() == Visibilities.PRIVATE) {
@@ -122,12 +134,24 @@ class PureKotlinCodeBlockModificationListener(project: Project) : Disposable {
// case like `fun foo(): String {...<caret>...}`
return blockDeclaration.bodyExpression
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
?.let {
if (parentClassOrObject == directParentClassOrObject) {
BlockModificationScopeElement(blockDeclaration, it)
} else if (parentClassOrObject != null) {
BlockModificationScopeElement(parentClassOrObject, it)
} else null
}
} else if (blockDeclaration.hasDeclaredReturnType()) {
// case like `fun foo(): String = b<caret>labla`
return blockDeclaration.initializer
?.takeIf { it.isAncestor(element) }
?.let { BlockModificationScopeElement(blockDeclaration, it) }
?.let {
if (parentClassOrObject == directParentClassOrObject) {
BlockModificationScopeElement(blockDeclaration, it)
} else if (parentClassOrObject != null) {
BlockModificationScopeElement(parentClassOrObject, it)
} else null
}
}
}
@@ -155,7 +179,11 @@ class PureKotlinCodeBlockModificationListener(project: Project) : Disposable {
blockDeclaration.findTopmostParentOfType<KtClassOrObject>() as? KtElement
if (declaration != null) {
return BlockModificationScopeElement(declaration, properExpression)
return if (parentClassOrObject == directParentClassOrObject) {
BlockModificationScopeElement(declaration, properExpression)
} else if (parentClassOrObject != null) {
BlockModificationScopeElement(parentClassOrObject, properExpression)
} else null
}
}
}
@@ -175,8 +203,12 @@ class PureKotlinCodeBlockModificationListener(project: Project) : Disposable {
blockDeclaration
.takeIf { it.isAncestor(element) }
?.let { ktClassInitializer ->
(PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java))?.let {
return BlockModificationScopeElement(it, ktClassInitializer)
parentClassOrObject?.let {
return if (parentClassOrObject == directParentClassOrObject) {
BlockModificationScopeElement(it, ktClassInitializer)
} else {
BlockModificationScopeElement(parentClassOrObject, ktClassInitializer)
}
}
}
}
@@ -185,8 +217,12 @@ class PureKotlinCodeBlockModificationListener(project: Project) : Disposable {
blockDeclaration.takeIf {
it.bodyExpression?.isAncestor(element) ?: false || it.getDelegationCallOrNull()?.isAncestor(element) ?: false
}?.let { ktConstructor ->
PsiTreeUtil.getParentOfType(blockDeclaration, KtClassOrObject::class.java)?.let {
return BlockModificationScopeElement(it, ktConstructor)
parentClassOrObject?.let {
return if (parentClassOrObject == directParentClassOrObject) {
BlockModificationScopeElement(it, ktConstructor)
} else {
BlockModificationScopeElement(parentClassOrObject, ktConstructor)
}
}
}
}
@@ -36,8 +36,9 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
ktFile.text,
SKIP_ANALYZE_CHECK_DIRECTIVE
)
val project = myFixture.project
val tracker =
PsiManager.getInstance(myFixture.project).modificationTracker as PsiModificationTrackerImpl
PsiManager.getInstance(project).modificationTracker as PsiModificationTrackerImpl
val element = ktFile.findElementAt(myFixture.caretOffset)
assertNotNull("Should be valid element", element)
val oobBeforeType = ktFile.outOfBlockModificationCount
@@ -47,7 +48,7 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
ktFile.analyzeWithAllCompilerChecks()
myFixture.type(stringToType)
PsiDocumentManager.getInstance(myFixture.project).commitDocument(myFixture.getDocument(myFixture.file))
PsiDocumentManager.getInstance(project).commitDocument(myFixture.getDocument(myFixture.file))
val oobAfterCount = ktFile.outOfBlockModificationCount
val modificationCountAfterType = tracker.modificationCount
assertTrue(
@@ -60,6 +61,7 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
expectedOutOfBlock, oobBeforeType != oobAfterCount
)
checkForUnexpectedErrors(ktFile)
DirectiveBasedActionUtils.inspectionChecks(name, ktFile)
if (!isSkipCheckDefined) {
checkOOBWithDescriptorsResolve(expectedOutOfBlock)
@@ -368,6 +368,11 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
runTest("testData/codeInsight/outOfBlock/InitBlockInObject.kt");
}
@TestMetadata("InnerClassInnerMethod.kt")
public void testInnerClassInnerMethod() throws Exception {
runTest("testData/codeInsight/outOfBlock/InnerClassInnerMethod.kt");
}
@TestMetadata("LocalFunWithBody.kt")
public void testLocalFunWithBody() throws Exception {
runTest("testData/codeInsight/outOfBlock/LocalFunWithBody.kt");
@@ -0,0 +1,11 @@
// OUT_OF_CODE_BLOCK: FALSE
// TYPE: '\b"a"'
// INSPECTION-CLASS: org.jetbrains.kotlin.idea.inspections.RedundantInnerClassModifierInspection
// INSPECTION: [LIKE_UNUSED_SYMBOL:6] Redundant 'inner' modifier
class RedundantInner(private val s: String) {
private inner class D {
fun f(): Int {
return s<caret>.length
}
}
}
@@ -2,12 +2,17 @@
package org.jetbrains.kotlin.idea.test
import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.progress.ProgressManager
import com.intellij.psi.PsiFile
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.test.InTextDirectivesUtils
@@ -66,6 +71,43 @@ object DirectiveBasedActionUtils {
)
}
fun inspectionChecks(name: String, file: PsiFile) {
InTextDirectivesUtils.findLinesWithPrefixesRemoved(file.text, "// INSPECTION-CLASS:").takeIf { it.isNotEmpty() }?.let { inspectionNames ->
val inspectionManager = InspectionManager.getInstance(file.project)
val inspections = inspectionNames.map { Class.forName(it).getDeclaredConstructor().newInstance() as AbstractKotlinInspection }
val problems = mutableListOf<ProblemDescriptor>()
ProgressManager.getInstance().executeProcessUnderProgress(
{
for (inspection in inspections) {
problems += inspection.processFile(
file,
inspectionManager
)
}
}, DaemonProgressIndicator()
)
val directive = "// INSPECTION:"
val expected = InTextDirectivesUtils.findLinesWithPrefixesRemoved(file.text, directive)
.sorted()
.map { "$directive $it" }
val actual = problems
// lineNumber is 0-based
.map { "$directive [${it.highlightType.name}:${it.lineNumber + 1}] $it" }
.sorted()
if (actual.isEmpty() && expected.isEmpty()) return
KotlinLightCodeInsightFixtureTestCaseBase.assertOrderedEquals(
"All actual $name should be mentioned in test data with '$directive' directive. " +
"But no unnecessary $name should be me mentioned, file:\n${file.text}",
actual,
expected
)
}
}
fun checkAvailableActionsAreExpected(file: PsiFile, availableActions: Collection<IntentionAction>) {
val expectedActions = InTextDirectivesUtils.findLinesWithPrefixesRemoved(file.text, "// ACTION:").sorted()