Drop UNREACHABLE_CODE eager text range calculation
#KT-35242 Fixed
This commit is contained in:
@@ -281,7 +281,7 @@ class ControlFlowInformationProvider private constructor(
|
||||
|
||||
private fun reportUnreachableCode(unreachableCode: UnreachableCode) {
|
||||
for (element in unreachableCode.elements) {
|
||||
trace.report(Errors.UNREACHABLE_CODE.on(element, unreachableCode.getUnreachableTextRanges(element)))
|
||||
trace.report(Errors.UNREACHABLE_CODE.on(element, unreachableCode.reachableElements, unreachableCode.unreachableElements))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,97 +29,112 @@ import java.util.*
|
||||
|
||||
interface UnreachableCode {
|
||||
val elements: Set<KtElement>
|
||||
fun getUnreachableTextRanges(element: KtElement): List<TextRange>
|
||||
val reachableElements: Set<KtElement>
|
||||
val unreachableElements: Set<KtElement>
|
||||
|
||||
companion object {
|
||||
fun getUnreachableTextRanges(
|
||||
element: KtElement,
|
||||
reachableElements: Set<KtElement>,
|
||||
unreachableElements: Set<KtElement>
|
||||
): List<TextRange> {
|
||||
return if (element.hasChildrenInSet(reachableElements)) {
|
||||
with(
|
||||
element.getLeavesOrReachableChildren(reachableElements, unreachableElements)
|
||||
.removeReachableElementsWithMeaninglessSiblings(reachableElements).mergeAdjacentTextRanges()
|
||||
) {
|
||||
if (isNotEmpty()) this
|
||||
// Specific case like condition in when:
|
||||
// element is dead but its only child is alive and has the same text range
|
||||
else listOf(element.textRange.endOffset.let { TextRange(it, it) })
|
||||
}
|
||||
} else {
|
||||
listOf(element.textRange!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtElement.hasChildrenInSet(set: Set<KtElement>): Boolean =
|
||||
PsiTreeUtil.collectElements(this) { it != this }.any { it in set }
|
||||
|
||||
private fun KtElement.getLeavesOrReachableChildren(
|
||||
reachableElements: Set<KtElement>,
|
||||
unreachableElements: Set<KtElement>
|
||||
): List<PsiElement> {
|
||||
val children = ArrayList<PsiElement>()
|
||||
acceptChildren(object : PsiElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
val isReachable =
|
||||
element is KtElement && reachableElements.contains(element) && !element.hasChildrenInSet(unreachableElements)
|
||||
if (isReachable || element.children.isEmpty()) {
|
||||
children.add(element)
|
||||
} else {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
}
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
private fun List<PsiElement>.removeReachableElementsWithMeaninglessSiblings(reachableElements: Set<KtElement>): List<PsiElement> {
|
||||
fun PsiElement.isMeaningless() = this is PsiWhiteSpace
|
||||
|| this.node?.elementType == KtTokens.COMMA
|
||||
|| this is PsiComment
|
||||
|
||||
val childrenToRemove = HashSet<PsiElement>()
|
||||
fun collectSiblingsIfMeaningless(elementIndex: Int, direction: Int) {
|
||||
val index = elementIndex + direction
|
||||
if (index !in 0 until size) return
|
||||
|
||||
val element = this[index]
|
||||
if (element.isMeaningless()) {
|
||||
childrenToRemove.add(element)
|
||||
collectSiblingsIfMeaningless(index, direction)
|
||||
}
|
||||
}
|
||||
for ((index, element) in this.withIndex()) {
|
||||
if (reachableElements.contains(element)) {
|
||||
childrenToRemove.add(element)
|
||||
collectSiblingsIfMeaningless(index, -1)
|
||||
collectSiblingsIfMeaningless(index, 1)
|
||||
}
|
||||
}
|
||||
return this.filter { it !in childrenToRemove }
|
||||
}
|
||||
|
||||
|
||||
private fun List<PsiElement>.mergeAdjacentTextRanges(): List<TextRange> {
|
||||
val result = ArrayList<TextRange>()
|
||||
val lastRange = fold(null as TextRange?) { currentTextRange, element ->
|
||||
|
||||
val elementRange = element.textRange!!
|
||||
when {
|
||||
currentTextRange == null -> {
|
||||
elementRange
|
||||
}
|
||||
currentTextRange.endOffset == elementRange.startOffset -> {
|
||||
currentTextRange.union(elementRange)
|
||||
}
|
||||
else -> {
|
||||
result.add(currentTextRange)
|
||||
elementRange
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastRange != null) {
|
||||
result.add(lastRange)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UnreachableCodeImpl(
|
||||
private val reachableElements: Set<KtElement>,
|
||||
private val unreachableElements: Set<KtElement>
|
||||
override val reachableElements: Set<KtElement>,
|
||||
override val unreachableElements: Set<KtElement>
|
||||
) : UnreachableCode {
|
||||
|
||||
// This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well
|
||||
override val elements: Set<KtElement> = KtPsiUtil.findRootExpressions(unreachableElements)
|
||||
|
||||
override fun getUnreachableTextRanges(element: KtElement): List<TextRange> {
|
||||
return if (element.hasChildrenInSet(reachableElements)) {
|
||||
with(element.getLeavesOrReachableChildren().removeReachableElementsWithMeaninglessSiblings().mergeAdjacentTextRanges()) {
|
||||
if (isNotEmpty()) this
|
||||
// Specific case like condition in when:
|
||||
// element is dead but its only child is alive and has the same text range
|
||||
else listOf(element.textRange.endOffset.let { TextRange(it, it) })
|
||||
}
|
||||
} else {
|
||||
listOf(element.textRange!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtElement.hasChildrenInSet(set: Set<KtElement>): Boolean =
|
||||
PsiTreeUtil.collectElements(this) { it != this }.any { it in set }
|
||||
|
||||
private fun KtElement.getLeavesOrReachableChildren(): List<PsiElement> {
|
||||
val children = ArrayList<PsiElement>()
|
||||
acceptChildren(object : PsiElementVisitor() {
|
||||
override fun visitElement(element: PsiElement) {
|
||||
val isReachable =
|
||||
element is KtElement && reachableElements.contains(element) && !element.hasChildrenInSet(unreachableElements)
|
||||
if (isReachable || element.children.isEmpty()) {
|
||||
children.add(element)
|
||||
} else {
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
}
|
||||
})
|
||||
return children
|
||||
}
|
||||
|
||||
private fun List<PsiElement>.removeReachableElementsWithMeaninglessSiblings(): List<PsiElement> {
|
||||
fun PsiElement.isMeaningless() = this is PsiWhiteSpace
|
||||
|| this.node?.elementType == KtTokens.COMMA
|
||||
|| this is PsiComment
|
||||
|
||||
val childrenToRemove = HashSet<PsiElement>()
|
||||
fun collectSiblingsIfMeaningless(elementIndex: Int, direction: Int) {
|
||||
val index = elementIndex + direction
|
||||
if (index !in 0..(size - 1)) return
|
||||
|
||||
val element = this[index]
|
||||
if (element.isMeaningless()) {
|
||||
childrenToRemove.add(element)
|
||||
collectSiblingsIfMeaningless(index, direction)
|
||||
}
|
||||
}
|
||||
for ((index, element) in this.withIndex()) {
|
||||
if (reachableElements.contains(element)) {
|
||||
childrenToRemove.add(element)
|
||||
collectSiblingsIfMeaningless(index, -1)
|
||||
collectSiblingsIfMeaningless(index, 1)
|
||||
}
|
||||
}
|
||||
return this.filter { it !in childrenToRemove }
|
||||
}
|
||||
|
||||
|
||||
private fun List<PsiElement>.mergeAdjacentTextRanges(): List<TextRange> {
|
||||
val result = ArrayList<TextRange>()
|
||||
val lastRange = fold(null as TextRange?) { currentTextRange, element ->
|
||||
|
||||
val elementRange = element.textRange!!
|
||||
when {
|
||||
currentTextRange == null -> {
|
||||
elementRange
|
||||
}
|
||||
currentTextRange.endOffset == elementRange.startOffset -> {
|
||||
currentTextRange.union(elementRange)
|
||||
}
|
||||
else -> {
|
||||
result.add(currentTextRange)
|
||||
elementRange
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastRange != null) {
|
||||
result.add(lastRange)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package org.jetbrains.kotlin.diagnostics;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.source.tree.LeafPsiElement;
|
||||
import kotlin.Pair;
|
||||
@@ -37,6 +36,7 @@ import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.jetbrains.kotlin.diagnostics.PositioningStrategies.*;
|
||||
import static org.jetbrains.kotlin.diagnostics.Severity.*;
|
||||
@@ -886,7 +886,7 @@ public interface Errors {
|
||||
|
||||
// Control flow / Data flow
|
||||
|
||||
DiagnosticFactory1<KtElement, List<TextRange>> UNREACHABLE_CODE = DiagnosticFactory1.create(
|
||||
DiagnosticFactory2<KtElement, Set<KtElement>, Set<KtElement>> UNREACHABLE_CODE = DiagnosticFactory2.create(
|
||||
WARNING, PositioningStrategies.UNREACHABLE_CODE);
|
||||
|
||||
DiagnosticFactory0<KtVariableDeclaration> VARIABLE_WITH_NO_TYPE_NO_INITIALIZER = DiagnosticFactory0.create(ERROR, DECLARATION_NAME);
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiNameIdentifierOwner
|
||||
import com.intellij.psi.tree.TokenSet
|
||||
import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.cfg.UnreachableCode
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.ACTUAL_WITHOUT_EXPECT
|
||||
import org.jetbrains.kotlin.diagnostics.Errors.NO_ACTUAL_FOR_EXPECT
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
@@ -579,7 +580,8 @@ object PositioningStrategies {
|
||||
@JvmField
|
||||
val UNREACHABLE_CODE: PositioningStrategy<PsiElement> = object : PositioningStrategy<PsiElement>() {
|
||||
override fun markDiagnostic(diagnostic: ParametrizedDiagnostic<out PsiElement>): List<TextRange> {
|
||||
return Errors.UNREACHABLE_CODE.cast(diagnostic).a
|
||||
val unreachableCode = Errors.UNREACHABLE_CODE.cast(diagnostic)
|
||||
return UnreachableCode.getUnreachableTextRanges(unreachableCode.psiElement, unreachableCode.a, unreachableCode.b)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -351,7 +351,7 @@ public class DefaultErrorMessages {
|
||||
MAP.put(VAL_OR_VAR_ON_CATCH_PARAMETER, "''{0}'' on catch parameter is not allowed", TO_STRING);
|
||||
MAP.put(VAL_OR_VAR_ON_SECONDARY_CONSTRUCTOR_PARAMETER, "''{0}'' on secondary constructor parameter is not allowed", TO_STRING);
|
||||
|
||||
MAP.put(UNREACHABLE_CODE, "Unreachable code", TO_STRING);
|
||||
MAP.put(UNREACHABLE_CODE, "Unreachable code", EMPTY, EMPTY);
|
||||
|
||||
MAP.put(MANY_COMPANION_OBJECTS, "Only one companion object is allowed per class");
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ object Renderers {
|
||||
element.toString()
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val EMPTY = Renderer<Any> { "" }
|
||||
|
||||
@JvmField
|
||||
val STRING = Renderer<String> { it }
|
||||
|
||||
|
||||
@@ -620,6 +620,10 @@ fun main(args: Array<String>) {
|
||||
model("codeInsight/outOfBlock", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractChangeLocalityDetectorTest> {
|
||||
model("codeInsight/changeLocality", pattern = KT_OR_KTS)
|
||||
}
|
||||
|
||||
testClass<AbstractDataFlowValueRenderingTest> {
|
||||
model("dataFlowValueRendering")
|
||||
}
|
||||
|
||||
+20
-8
@@ -93,8 +93,9 @@ class KotlinCodeBlockModificationListener(
|
||||
|
||||
// skip change if it contains only virtual/fake change
|
||||
if (changedElements.isNotEmpty() &&
|
||||
// ignore formatting (whitespaces etc)
|
||||
// ignore formatting (whitespaces etc) change
|
||||
(isFormattingChange(changeSet) ||
|
||||
// ignore comment change
|
||||
isCommentChange(changeSet) ||
|
||||
changedElements.all { !it.psi.isPhysical })
|
||||
) return
|
||||
@@ -215,6 +216,20 @@ class KotlinCodeBlockModificationListener(
|
||||
changeSet.getChangesByElement(it).affectedChildren.all { c -> c is PsiWhiteSpace }
|
||||
}
|
||||
|
||||
/**
|
||||
* Has to be aligned with [getInsideCodeBlockModificationScope] :
|
||||
*
|
||||
* result of analysis has to be reflected in dirty scope,
|
||||
* the only difference is whitespaces and comments
|
||||
*/
|
||||
fun getInsideCodeBlockModificationDirtyScope(element: PsiElement): PsiElement? {
|
||||
if (!element.isPhysical) return null
|
||||
// dirty scope for whitespaces and comments is the element itself
|
||||
if (element is PsiWhiteSpace || element is PsiComment || element is KDoc) return element
|
||||
|
||||
return getInsideCodeBlockModificationScope(element)?.blockDeclaration ?: null
|
||||
}
|
||||
|
||||
fun getInsideCodeBlockModificationScope(element: PsiElement): BlockModificationScopeElement? {
|
||||
val lambda = element.getTopmostParentOfType<KtLambdaExpression>()
|
||||
if (lambda is KtLambdaExpression) {
|
||||
@@ -259,13 +274,10 @@ class KotlinCodeBlockModificationListener(
|
||||
val declaration = if (blockDeclaration.initializer != null)
|
||||
blockDeclaration
|
||||
else
|
||||
KtPsiUtil.getTopmostParentOfTypes(
|
||||
blockDeclaration,
|
||||
// property could be initialized on a class level
|
||||
KtClass::class.java,
|
||||
// ktFile to check top level property declarations
|
||||
KtFile::class.java
|
||||
) as KtElement
|
||||
// property could be initialized on a class level
|
||||
KtPsiUtil.getTopmostParentOfTypes(blockDeclaration, KtClass::class.java) as? KtElement ?:
|
||||
// ktFile to check top level property declarations
|
||||
return null
|
||||
return BlockModificationScopeElement(declaration, expression)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -18,13 +18,12 @@ package org.jetbrains.kotlin.idea.highlighter
|
||||
|
||||
import com.intellij.codeInsight.daemon.ChangeLocalityDetector
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener.Companion.getInsideCodeBlockModificationScope
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.KotlinCodeBlockModificationListener.Companion.getInsideCodeBlockModificationDirtyScope
|
||||
|
||||
class KotlinChangeLocalityDetector : ChangeLocalityDetector {
|
||||
override fun getChangeHighlightingDirtyScopeFor(element: PsiElement): PsiElement? {
|
||||
val modificationScope =
|
||||
getInsideCodeBlockModificationScope(element) ?: return null
|
||||
|
||||
return modificationScope.blockDeclaration
|
||||
// in some cases it returns a bit wider scope for the element as it is not possible to track changes here
|
||||
// e.g.: delete a space in expression `foo( )` results to entire expression `foo()`
|
||||
return getInsideCodeBlockModificationDirtyScope(element)
|
||||
}
|
||||
}
|
||||
+24
-6
@@ -21,18 +21,36 @@ object DirectiveBasedActionUtils {
|
||||
return
|
||||
}
|
||||
|
||||
val expectedErrors = InTextDirectivesUtils.findLinesWithPrefixesRemoved(file.text, "// ERROR:").sorted()
|
||||
checkForUnexpected(file, diagnosticsProvider, "// ERROR:", "errors", Severity.ERROR)
|
||||
}
|
||||
|
||||
fun checkForUnexpectedWarnings(file: KtFile, diagnosticsProvider: (KtFile) -> Diagnostics = { it.analyzeWithContent().diagnostics }) {
|
||||
if (InTextDirectivesUtils.findLinesWithPrefixesRemoved(file.text, "// ENABLE-WARNINGS").isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
checkForUnexpected(file, diagnosticsProvider, "// WARNING:", "warnings", Severity.WARNING)
|
||||
}
|
||||
|
||||
private fun checkForUnexpected(
|
||||
file: KtFile,
|
||||
diagnosticsProvider: (KtFile) -> Diagnostics,
|
||||
directive: String,
|
||||
name: String,
|
||||
severity: Severity
|
||||
) {
|
||||
val expected = InTextDirectivesUtils.findLinesWithPrefixesRemoved(file.text, directive).sorted()
|
||||
|
||||
val diagnostics = diagnosticsProvider(file)
|
||||
val actualErrors = diagnostics
|
||||
.filter { it.severity == Severity.ERROR }
|
||||
val actual = diagnostics
|
||||
.filter { it.severity == severity }
|
||||
.map { DefaultErrorMessages.render(it).replace("\n", "<br>") }
|
||||
.sorted()
|
||||
|
||||
UsefulTestCase.assertOrderedEquals(
|
||||
"All actual errors should be mentioned in test data with // ERROR: directive. But no unnecessary errors should be me mentioned",
|
||||
actualErrors,
|
||||
expectedErrors
|
||||
"All actual $name should be mentioned in test data with '$directive' directive. But no unnecessary $name should be me mentioned",
|
||||
actual,
|
||||
expected
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// SCOPE: '// some change'
|
||||
|
||||
class Comment {
|
||||
fun q() {
|
||||
|
||||
}
|
||||
|
||||
val someValue: String
|
||||
get() {
|
||||
return "X"
|
||||
}
|
||||
|
||||
fun baa() {
|
||||
<selection>// some change</selection>
|
||||
val list = listOf<String>(TODO(""))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// SCOPE: 'fun baa() {'
|
||||
// SCOPE: ' val list = listOf<String>("Z")'
|
||||
// SCOPE: ' }'
|
||||
|
||||
class Comment {
|
||||
fun q() {
|
||||
|
||||
}
|
||||
|
||||
val someValue: String
|
||||
get() {
|
||||
return "X"
|
||||
}
|
||||
|
||||
fun baa() {
|
||||
val list = listOf<String>(<selection>"Z"</selection>)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// OUT_OF_CODE_BLOCK: FALSE
|
||||
// ERROR: Unresolved reference: awhen
|
||||
// OUT_OF_CODE_BLOCK: TRUE
|
||||
// TYPE: 'z'
|
||||
// ERROR: Unresolved reference: zwhen
|
||||
|
||||
enum class A {
|
||||
e1, e2, e3
|
||||
}
|
||||
@@ -12,4 +14,7 @@ val B.foo: Int
|
||||
A.e1 -> 1
|
||||
A.e2 -> 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Investigate
|
||||
// SKIP_ANALYZE_CHECK
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// OUT_OF_CODE_BLOCK: FALSE
|
||||
// TYPE: '1'
|
||||
// ENABLE-WARNINGS
|
||||
// WARNING: Unreachable code
|
||||
|
||||
fun test() {
|
||||
return
|
||||
|
||||
val a = <caret>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// OUT_OF_CODE_BLOCK: FALSE
|
||||
// OUT_OF_CODE_BLOCK: TRUE
|
||||
// TYPE: '//'
|
||||
// ERROR: Property must be initialized
|
||||
|
||||
@@ -6,4 +6,7 @@ var prop1: Int
|
||||
set(value) {
|
||||
<caret> println("prop.setter")
|
||||
field = value
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Investigate
|
||||
// SKIP_ANALYZE_CHECK
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.codeInsight
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.idea.highlighter.KotlinChangeLocalityDetector
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
|
||||
private const val SCOPE_DIRECTIVE = "// SCOPE:"
|
||||
|
||||
abstract class AbstractChangeLocalityDetectorTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
|
||||
protected fun doTest(unused: String?) {
|
||||
val ktFile = myFixture.configureByFile(fileName()) as KtFile
|
||||
|
||||
val expectedScopeLines = InTextDirectivesUtils.findListWithPrefixes(ktFile.text, SCOPE_DIRECTIVE)
|
||||
assertTrue("scope has to be specified with $SCOPE_DIRECTIVE", expectedScopeLines.isNotEmpty())
|
||||
val selectionModel = editor.selectionModel
|
||||
assertTrue("changed item has to be specified in <selection></selection", selectionModel.hasSelection())
|
||||
|
||||
val element = PsiTreeUtil.findElementOfClassAtRange(
|
||||
ktFile,
|
||||
selectionModel.selectionStart,
|
||||
selectionModel.selectionEnd,
|
||||
PsiElement::class.java
|
||||
)
|
||||
?: error("No PsiElement at selection range")
|
||||
val changeLocalityDetector = KotlinChangeLocalityDetector()
|
||||
|
||||
val dirtyScope = changeLocalityDetector.getChangeHighlightingDirtyScopeFor(element)
|
||||
?: error("scope has to be calculated")
|
||||
println("dirtyScopeFor ${element.text} is ${dirtyScope?.text}")
|
||||
assertEquals(expectedScopeLines.joinToString("\n"), dirtyScope.text)
|
||||
}
|
||||
}
|
||||
+14
-12
@@ -13,7 +13,6 @@ import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.trackers.outOfBlockModificationCount
|
||||
@@ -22,6 +21,7 @@ import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.kdoc.psi.api.KDoc
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
|
||||
@@ -33,14 +33,14 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
|
||||
ktFile.text,
|
||||
SKIP_ANALYZE_CHECK_DIRECTIVE
|
||||
)
|
||||
TestCase.assertTrue(
|
||||
assertTrue(
|
||||
"It's allowed to skip check with analyze only for tests where out-of-block is expected",
|
||||
!isSkipCheckDefined || expectedOutOfBlock
|
||||
)
|
||||
val tracker =
|
||||
PsiManager.getInstance(myFixture.project).modificationTracker as PsiModificationTrackerImpl
|
||||
val element = ktFile.findElementAt(myFixture.caretOffset)
|
||||
TestCase.assertNotNull("Should be valid element", element)
|
||||
assertNotNull("Should be valid element", element)
|
||||
val oobBeforeType = ktFile.outOfBlockModificationCount
|
||||
val modificationCountBeforeType = tracker.modificationCount
|
||||
|
||||
@@ -51,11 +51,11 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
|
||||
PsiDocumentManager.getInstance(myFixture.project).commitDocument(myFixture.getDocument(myFixture.file))
|
||||
val oobAfterCount = ktFile.outOfBlockModificationCount
|
||||
val modificationCountAfterType = tracker.modificationCount
|
||||
TestCase.assertTrue(
|
||||
assertTrue(
|
||||
"Modification tracker should always be changed after type",
|
||||
modificationCountBeforeType != modificationCountAfterType
|
||||
)
|
||||
TestCase.assertEquals(
|
||||
assertEquals(
|
||||
"Result for out of block test is differs from expected on element in file:\n"
|
||||
+ FileUtil.loadFile(testDataFile()),
|
||||
expectedOutOfBlock, oobBeforeType != oobAfterCount
|
||||
@@ -68,7 +68,9 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
|
||||
}
|
||||
|
||||
private fun checkForUnexpectedErrors(ktFile: KtFile) {
|
||||
DirectiveBasedActionUtils.checkForUnexpectedErrors(ktFile) { it.analyzeWithAllCompilerChecks().bindingContext.diagnostics }
|
||||
val diagnosticsProvider: (KtFile) -> Diagnostics = { it.analyzeWithAllCompilerChecks().bindingContext.diagnostics }
|
||||
DirectiveBasedActionUtils.checkForUnexpectedWarnings(ktFile, diagnosticsProvider)
|
||||
DirectiveBasedActionUtils.checkForUnexpectedErrors(ktFile, diagnosticsProvider)
|
||||
}
|
||||
|
||||
private fun checkOOBWithDescriptorsResolve(expectedOutOfBlock: Boolean) {
|
||||
@@ -83,14 +85,14 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
|
||||
val ktElement = ktExpression ?: ktDeclaration ?: return
|
||||
val facade = ktElement.containingKtFile.getResolutionFacade()
|
||||
val session = facade.getFrontendService(ResolveSession::class.java)
|
||||
|
||||
session.forceResolveAll()
|
||||
val context = session.bindingContext
|
||||
if (ktExpression != null && ktExpression !== ktDeclaration) {
|
||||
val expressionProcessed = context.get(
|
||||
BindingContext.PROCESSED,
|
||||
if (ktExpression is KtFunctionLiteral) ktExpression.getParent() as KtLambdaExpression else ktExpression
|
||||
) === java.lang.Boolean.TRUE
|
||||
TestCase.assertEquals(
|
||||
val expression = if (ktExpression is KtFunctionLiteral) ktExpression.getParent() as KtLambdaExpression else ktExpression
|
||||
val processed = context.get(BindingContext.PROCESSED, expression)
|
||||
val expressionProcessed = processed === java.lang.Boolean.TRUE
|
||||
assertEquals(
|
||||
"Expected out-of-block should result expression analyzed and vise versa", expectedOutOfBlock,
|
||||
expressionProcessed
|
||||
)
|
||||
@@ -100,7 +102,7 @@ abstract class AbstractOutOfBlockModificationTest : KotlinLightCodeInsightFixtur
|
||||
BindingContext.DECLARATION_TO_DESCRIPTOR,
|
||||
ktDeclaration
|
||||
) != null
|
||||
TestCase.assertEquals(
|
||||
assertEquals(
|
||||
"Expected out-of-block should result declaration analyzed and vise versa", expectedOutOfBlock,
|
||||
declarationProcessed
|
||||
)
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.codeInsight;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/codeInsight/changeLocality")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class ChangeLocalityDetectorTestGenerated extends AbstractChangeLocalityDetectorTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInChangeLocality() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/changeLocality"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("Comment.kt")
|
||||
public void testComment() throws Exception {
|
||||
runTest("idea/testData/codeInsight/changeLocality/Comment.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("InMethod.kt")
|
||||
public void testInMethod() throws Exception {
|
||||
runTest("idea/testData/codeInsight/changeLocality/InMethod.kt");
|
||||
}
|
||||
}
|
||||
+5
@@ -178,6 +178,11 @@ public class OutOfBlockModificationTestGenerated extends AbstractOutOfBlockModif
|
||||
runTest("idea/testData/codeInsight/outOfBlock/InMethod.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("InMethodUnreacableCode.kt")
|
||||
public void testInMethodUnreacableCode() throws Exception {
|
||||
runTest("idea/testData/codeInsight/outOfBlock/InMethodUnreacableCode.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("InNestedClass.kt")
|
||||
public void testInNestedClass() throws Exception {
|
||||
runTest("idea/testData/codeInsight/outOfBlock/InNestedClass.kt");
|
||||
|
||||
Reference in New Issue
Block a user