Test local inspections on the fly via highlighting
Fixes problem with INFORMATION highlight type in batch mode (IDEA 181). It's no more required to report INFORMATIONs in unit tests in batch.
This commit is contained in:
+1
-2
@@ -66,8 +66,7 @@ abstract class AbstractKotlinInspection: LocalInspectionTool(), CustomSuppressab
|
|||||||
range: TextRange?,
|
range: TextRange?,
|
||||||
vararg fixes: LocalQuickFix
|
vararg fixes: LocalQuickFix
|
||||||
) {
|
) {
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode &&
|
if (!isOnTheFly && highlightType == ProblemHighlightType.INFORMATION) return
|
||||||
!isOnTheFly && highlightType == ProblemHighlightType.INFORMATION) return
|
|
||||||
val problemDescriptor = manager.createProblemDescriptor(element, range, description, highlightType, isOnTheFly, *fixes)
|
val problemDescriptor = manager.createProblemDescriptor(element, range, description, highlightType, isOnTheFly, *fixes)
|
||||||
registerProblem(problemDescriptor)
|
registerProblem(problemDescriptor)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,21 +31,5 @@
|
|||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
||||||
<description>Equality check should be used instead of elvis for nullable boolean check</description>
|
<description>Equality check should be used instead of elvis for nullable boolean check</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
|
||||||
<file>test.kt</file>
|
|
||||||
<line>13</line>
|
|
||||||
<module>light_idea_test_case</module>
|
|
||||||
<entry_point TYPE="file" FQNAME="test.kt"/>
|
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
|
||||||
<description>Equality check can be used instead of elvis for nullable boolean check</description>
|
|
||||||
</problem>
|
|
||||||
<problem>
|
|
||||||
<file>test.kt</file>
|
|
||||||
<line>14</line>
|
|
||||||
<module>light_idea_test_case</module>
|
|
||||||
<entry_point TYPE="file" FQNAME="test.kt"/>
|
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
|
||||||
<description>Equality check can be used instead of elvis for nullable boolean check</description>
|
|
||||||
</problem>
|
|
||||||
|
|
||||||
</problems>
|
</problems>
|
||||||
+2
-2
@@ -10,6 +10,6 @@ fun foo() {
|
|||||||
if (a ?: false || !(b ?: true)) {
|
if (a ?: false || !(b ?: true)) {
|
||||||
|
|
||||||
}
|
}
|
||||||
val x = a ?: false
|
val x = a ?: false // INFORMATION -- not reported in batch
|
||||||
val y = !(b ?: true)
|
val y = !(b ?: true) // INFORMATION -- not reported in batch
|
||||||
}
|
}
|
||||||
-16
@@ -31,20 +31,4 @@
|
|||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
||||||
<description>Call replaceable with binary operator</description>
|
<description>Call replaceable with binary operator</description>
|
||||||
</problem>
|
</problem>
|
||||||
<problem>
|
|
||||||
<file>test.kt</file>
|
|
||||||
<line>8</line>
|
|
||||||
<module>light_idea_test_case</module>
|
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
|
||||||
<description>Call replaceable with binary operator</description>
|
|
||||||
</problem>
|
|
||||||
<problem>
|
|
||||||
<file>test.kt</file>
|
|
||||||
<line>9</line>
|
|
||||||
<module>light_idea_test_case</module>
|
|
||||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
|
||||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
|
||||||
<description>Call replaceable with binary operator</description>
|
|
||||||
</problem>
|
|
||||||
</problems>
|
</problems>
|
||||||
@@ -5,6 +5,6 @@ fun foo() {
|
|||||||
1.compareTo(1) == 0 // NO
|
1.compareTo(1) == 0 // NO
|
||||||
2.compareTo(1) > 0 // YES
|
2.compareTo(1) > 0 // YES
|
||||||
0 >= 1.compareTo(2) // YES
|
0 >= 1.compareTo(2) // YES
|
||||||
2.plus(2) // YES (information)
|
2.plus(2) // YES (information) -- not reported in batch
|
||||||
2.times(2) // YES (information)
|
2.times(2) // YES (information) -- not reported in batch
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// HIGHLIGHT: INFORMATION
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
var a: Boolean? = null
|
var a: Boolean? = null
|
||||||
val x = a<caret> ?: false
|
val x = a<caret> ?: false
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// HIGHLIGHT: INFORMATION
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
var a: Boolean? = null
|
var a: Boolean? = null
|
||||||
val x = a == true
|
val x = a == true
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// HIGHLIGHT: INFORMATION
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
var b: Boolean? = null
|
var b: Boolean? = null
|
||||||
val x = !(b <caret>?: true)
|
val x = !(b <caret>?: true)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// HIGHLIGHT: INFORMATION
|
||||||
|
|
||||||
fun foo() {
|
fun foo() {
|
||||||
var b: Boolean? = null
|
var b: Boolean? = null
|
||||||
val x = b == false
|
val x = b == false
|
||||||
|
|||||||
@@ -17,11 +17,15 @@
|
|||||||
package org.jetbrains.kotlin.idea.inspections
|
package org.jetbrains.kotlin.idea.inspections
|
||||||
|
|
||||||
import com.google.common.collect.Lists
|
import com.google.common.collect.Lists
|
||||||
import com.intellij.codeInspection.ProblemDescriptor
|
import com.intellij.codeHighlighting.HighlightDisplayLevel
|
||||||
|
import com.intellij.codeHighlighting.Pass
|
||||||
|
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
||||||
|
import com.intellij.codeInsight.intention.EmptyIntentionAction
|
||||||
import com.intellij.codeInspection.ProblemHighlightType
|
import com.intellij.codeInspection.ProblemHighlightType
|
||||||
import com.intellij.openapi.util.SystemInfo
|
import com.intellij.openapi.util.SystemInfo
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager
|
||||||
|
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
||||||
import junit.framework.ComparisonFailure
|
import junit.framework.ComparisonFailure
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils
|
import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils
|
||||||
@@ -29,7 +33,6 @@ import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
|||||||
import org.jetbrains.kotlin.idea.test.configureCompilerOptions
|
import org.jetbrains.kotlin.idea.test.configureCompilerOptions
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
|
||||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
@@ -64,12 +67,16 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (candidateFiles.isEmpty()) {
|
if (candidateFiles.isEmpty()) {
|
||||||
throw AssertionError(".inspection file is not found for " + testDataFile +
|
throw AssertionError(
|
||||||
"\nAdd it to base directory of test data. It should contain fully-qualified name of inspection class.")
|
".inspection file is not found for " + testDataFile +
|
||||||
|
"\nAdd it to base directory of test data. It should contain fully-qualified name of inspection class."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (candidateFiles.size > 1) {
|
if (candidateFiles.size > 1) {
|
||||||
throw AssertionError("Several .inspection files are available for " + testDataFile +
|
throw AssertionError(
|
||||||
"\nPlease remove some of them\n" + candidateFiles)
|
"Several .inspection files are available for " + testDataFile +
|
||||||
|
"\nPlease remove some of them\n" + candidateFiles
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val className = FileUtil.loadFile(candidateFiles[0]).trim { it <= ' ' }
|
val className = FileUtil.loadFile(candidateFiles[0]).trim { it <= ' ' }
|
||||||
@@ -106,9 +113,9 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val psiFile = myFixture.configureByFiles(*(listOf(mainFile.name) + extraFileNames).toTypedArray()).first()
|
myFixture.configureByFiles(*(listOf(mainFile.name) + extraFileNames).toTypedArray()).first()
|
||||||
|
|
||||||
doTestFor(mainFile.name, psiFile.virtualFile!!, inspection, fileText)
|
doTestFor(mainFile.name, inspection, fileText)
|
||||||
|
|
||||||
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) {
|
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) {
|
||||||
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
|
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
|
||||||
@@ -116,90 +123,118 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fun runInspectionWithFixesAndCheck(
|
protected fun runInspectionWithFixesAndCheck(
|
||||||
file: VirtualFile,
|
inspection: AbstractKotlinInspection,
|
||||||
inspection: AbstractKotlinInspection,
|
expectedProblemString: String?,
|
||||||
problemExpectedString: String?,
|
expectedHighlightString: String?,
|
||||||
highlightExpectedString: String?,
|
localFixTextString: String?
|
||||||
localFixTextString: String?
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val problemExpected = problemExpectedString == null || problemExpectedString != "none"
|
val problemExpected = expectedProblemString == null || expectedProblemString != "none"
|
||||||
val presentation = runInspection(inspection, project, listOf(file))
|
myFixture.enableInspections(inspection::class.java)
|
||||||
val problemDescriptors = presentation.problemDescriptors
|
|
||||||
.filterIsInstance<ProblemDescriptor>()
|
// Set default level to WARNING to make possible to test DO_NOT_SHOW
|
||||||
.filter {
|
val inspectionProfileManager = ProjectInspectionProfileManager.getInstance(project)
|
||||||
val caretOffset = myFixture.caretOffset
|
val inspectionProfile = inspectionProfileManager.currentProfile
|
||||||
caretOffset in it.textRangeInElement?.shiftRight(it.psiElement.startOffset) ?: it.psiElement.textRange
|
val state = inspectionProfile.getToolDefaultState(inspection.shortName, project)
|
||||||
}
|
state.level = HighlightDisplayLevel.WARNING
|
||||||
|
|
||||||
|
val caretOffset = myFixture.caretOffset
|
||||||
|
val highlightInfos = CodeInsightTestFixtureImpl.instantiateAndRun(
|
||||||
|
file, editor, intArrayOf(
|
||||||
|
Pass.LINE_MARKERS,
|
||||||
|
Pass.EXTERNAL_TOOLS,
|
||||||
|
Pass.POPUP_HINTS,
|
||||||
|
Pass.UPDATE_ALL,
|
||||||
|
Pass.UPDATE_FOLDING,
|
||||||
|
Pass.WOLF
|
||||||
|
), false
|
||||||
|
).filter { it.description != null && caretOffset in it.startOffset..it.endOffset }
|
||||||
|
|
||||||
Assert.assertTrue(
|
Assert.assertTrue(
|
||||||
if (!problemExpected)
|
if (!problemExpected)
|
||||||
"No problems should be detected at caret\n" +
|
"No problems should be detected at caret\n" +
|
||||||
"Detected problems: ${problemDescriptors.joinToString { it.descriptionTemplate }}"
|
"Detected problems: ${highlightInfos.joinToString { it.description }}"
|
||||||
else
|
else
|
||||||
"Expected at least one problem at caret",
|
"Expected at least one problem at caret",
|
||||||
problemExpected == problemDescriptors.isNotEmpty())
|
problemExpected == highlightInfos.isNotEmpty()
|
||||||
if (!problemExpected) return false
|
)
|
||||||
problemDescriptors
|
if (!problemExpected || highlightInfos.isEmpty()) return false
|
||||||
.filter { it.highlightType != ProblemHighlightType.INFORMATION }
|
highlightInfos
|
||||||
.forEach {
|
.filter { it.type != HighlightInfoType.INFORMATION }
|
||||||
Assert.assertTrue("Problem description should not contain 'can': ${it.descriptionTemplate}",
|
.forEach {
|
||||||
" can " !in it.descriptionTemplate)
|
val description = it.description
|
||||||
}
|
Assert.assertTrue(
|
||||||
if (problemExpectedString != null) {
|
"Problem description should not contain 'can': $description",
|
||||||
Assert.assertTrue("Expected the following problem at caret: $problemExpectedString\n" +
|
" can " !in description
|
||||||
"Active problems: ${problemDescriptors.joinToString { it.descriptionTemplate }}",
|
)
|
||||||
problemDescriptors.any { it.descriptionTemplate == problemExpectedString })
|
}
|
||||||
|
|
||||||
|
if (expectedProblemString != null) {
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Expected the following problem at caret: $expectedProblemString\n" +
|
||||||
|
"Active problems: ${highlightInfos.joinToString { it.description }}",
|
||||||
|
highlightInfos.any { it.description == expectedProblemString }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (highlightExpectedString != null) {
|
val expectedHighlightType = when (expectedHighlightString) {
|
||||||
Assert.assertTrue("Expected the following problem highlight type\n" +
|
null -> null
|
||||||
"Actual types: ${problemDescriptors.joinToString { it.highlightType.toString() } }",
|
ProblemHighlightType.GENERIC_ERROR_OR_WARNING.name -> HighlightDisplayLevel.WARNING.name
|
||||||
problemDescriptors.all { it.highlightType.toString() == highlightExpectedString })
|
else -> expectedHighlightString
|
||||||
|
}
|
||||||
|
if (expectedHighlightType != null) {
|
||||||
|
Assert.assertTrue(
|
||||||
|
"Expected the following problem highlight type: $expectedHighlightType\n" +
|
||||||
|
"Actual type: ${highlightInfos.joinToString { it.type.toString() }}",
|
||||||
|
highlightInfos.all { expectedHighlightType in it.type.toString() }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val allLocalFixActions = problemDescriptors.flatMap { problem ->
|
val allLocalFixActions = highlightInfos.flatMap { it.quickFixActionMarkers }.map { it.first.action }
|
||||||
val fixes = problem.fixes
|
|
||||||
fixes?.toList() ?: emptyList()
|
|
||||||
}
|
|
||||||
val localFixActions = allLocalFixActions.filter { fix -> localFixTextString == null || fix.name == localFixTextString }
|
|
||||||
val availableDescription = allLocalFixActions.joinToString { it.name }
|
|
||||||
|
|
||||||
val fixDescription = localFixTextString?.let { "with specified text '$localFixTextString'"} ?: ""
|
val localFixActions = allLocalFixActions.filter { fix -> localFixTextString == null || fix.text == localFixTextString }
|
||||||
TestCase.assertTrue("No fix action $fixDescription\n" +
|
val availableDescription = allLocalFixActions.joinToString { it.text }
|
||||||
"Available actions: $availableDescription",
|
|
||||||
localFixActions.isNotEmpty())
|
|
||||||
|
|
||||||
val localFixAction = localFixActions.singleOrNull()
|
val fixDescription = localFixTextString?.let { "with specified text '$localFixTextString'" } ?: ""
|
||||||
TestCase.assertTrue("More than one fix action $fixDescription\n" +
|
TestCase.assertTrue(
|
||||||
"Available actions: $availableDescription",
|
"No fix action $fixDescription\n" +
|
||||||
localFixAction != null)
|
"Available actions: $availableDescription",
|
||||||
|
localFixActions.isNotEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
val problemDescriptor = problemDescriptors.find { localFixAction in it.fixes?.toList() ?: emptyList() }!!
|
val localFixAction = localFixActions.singleOrNull { it !is EmptyIntentionAction }
|
||||||
|
TestCase.assertTrue(
|
||||||
|
"More than one fix action $fixDescription\n" +
|
||||||
|
"Available actions: $availableDescription",
|
||||||
|
localFixAction != null
|
||||||
|
)
|
||||||
|
|
||||||
project.executeWriteCommand(localFixAction!!.name, null) {
|
project.executeWriteCommand(localFixAction!!.text, null) {
|
||||||
localFixAction.applyFix(project, problemDescriptor)
|
localFixAction.invoke(project, editor, file)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doTestFor(mainFilePath: String, file: VirtualFile, inspection: AbstractKotlinInspection, fileText: String) {
|
private fun doTestFor(mainFilePath: String, inspection: AbstractKotlinInspection, fileText: String) {
|
||||||
val problemExpectedString = InTextDirectivesUtils.findStringWithPrefixes(
|
val expectedProblemString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||||
fileText, "// $expectedProblemDirectiveName: ")
|
fileText, "// $expectedProblemDirectiveName: "
|
||||||
val highlightExpectedString = InTextDirectivesUtils.findStringWithPrefixes(
|
)
|
||||||
fileText, "// $expectedProblemHighlightType: ")
|
val expectedHighlightString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||||
|
fileText, "// $expectedProblemHighlightType: "
|
||||||
|
)
|
||||||
val localFixTextString = InTextDirectivesUtils.findStringWithPrefixes(
|
val localFixTextString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||||
fileText, "// $fixTextDirectiveName: ")
|
fileText, "// $fixTextDirectiveName: "
|
||||||
|
)
|
||||||
|
|
||||||
if (!runInspectionWithFixesAndCheck(file, inspection, problemExpectedString, highlightExpectedString, localFixTextString)) {
|
if (!runInspectionWithFixesAndCheck(inspection, expectedProblemString, expectedHighlightString, localFixTextString)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val canonicalPathToExpectedFile = mainFilePath + afterFileNameSuffix
|
val canonicalPathToExpectedFile = mainFilePath + afterFileNameSuffix
|
||||||
try {
|
try {
|
||||||
myFixture.checkResultByFile(canonicalPathToExpectedFile)
|
myFixture.checkResultByFile(canonicalPathToExpectedFile)
|
||||||
}
|
} catch (e: ComparisonFailure) {
|
||||||
catch (e: ComparisonFailure) {
|
|
||||||
KotlinTestUtils.assertEqualsToFile(
|
KotlinTestUtils.assertEqualsToFile(
|
||||||
File(testDataPath, canonicalPathToExpectedFile),
|
File(testDataPath, canonicalPathToExpectedFile),
|
||||||
editor.document.text
|
editor.document.text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -58,10 +58,10 @@ abstract class AbstractMultiFileLocalInspectionTest : AbstractLocalInspectionTes
|
|||||||
val problemExpectedString = config["problem"]?.asString // null means "some problem", "none" means no problem
|
val problemExpectedString = config["problem"]?.asString // null means "some problem", "none" means no problem
|
||||||
val localFixTextString = config["fix"]?.asString // null means "some single fix" or "none" if no problem expected
|
val localFixTextString = config["fix"]?.asString // null means "some single fix" or "none" if no problem expected
|
||||||
|
|
||||||
doTest(path) test@ { _ ->
|
doTest(path) test@{ _ ->
|
||||||
val mainFile = myFixture.configureFromTempProjectFile(mainFilePath)
|
myFixture.configureFromTempProjectFile(mainFilePath)
|
||||||
|
|
||||||
runInspectionWithFixesAndCheck(mainFile.virtualFile, inspection, problemExpectedString, null, localFixTextString)
|
runInspectionWithFixesAndCheck(inspection, problemExpectedString, null, localFixTextString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user