KT-32368 Rework Inline hints settings // migrate tests for lambdas

KotlinLambdasHintsProvider which in now responsible for lambda related
hints is not compatible with the existing LambdaReturnValueHintsTest.
Because of that tests were migrated to the new infrastructure.
This commit is contained in:
Andrei Klunnyi
2020-07-07 15:49:07 +02:00
parent 5fe1eaea17
commit b0dece756a
23 changed files with 288 additions and 438 deletions
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractCodeInsightActionT
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateHashCodeAndEqualsActionTest
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateTestSupportMethodActionTest
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateToStringActionTest
import org.jetbrains.kotlin.idea.codeInsight.hints.AbstractKotlinLambdasHintsProvider
import org.jetbrains.kotlin.idea.codeInsight.moveUpDown.AbstractMoveLeftRightTest
import org.jetbrains.kotlin.idea.codeInsight.moveUpDown.AbstractMoveStatementTest
import org.jetbrains.kotlin.idea.codeInsight.postfix.AbstractPostfixTemplateProviderTest
@@ -879,6 +880,10 @@ fun main(args: Array<String>) {
model("codeInsight/codeVision")
}
testClass<AbstractKotlinLambdasHintsProvider> {
model("codeInsight/hints/lambda")
}
testClass<AbstractScriptConfigurationHighlightingTest> {
model("script/definition/highlighting", extension = null, recursive = false)
model("script/definition/complex", extension = null, recursive = false, testMethod = "doComplexTest")
@@ -0,0 +1,16 @@
// MODE: return
@Target(AnnotationTarget.EXPRESSION)
annotation class Some
fun test() {
run {
val files: Any? = null
@Some
12<# ^run #>
}
run {
val files: Any? = null
@Some 12<# ^run #>
}
}
@@ -0,0 +1,5 @@
// MODE: off
val x = run {
println("foo")
1
}
+7
View File
@@ -0,0 +1,7 @@
// MODE: return
fun foo() {
run {
val length: Int? = null
length ?: 0<# ^run #>
}
}
+8
View File
@@ -0,0 +1,8 @@
// MODE: return
val x = run {
if (true) {
1<# ^run #>
} else {
0<# ^run #>
}
}
+4
View File
@@ -0,0 +1,4 @@
// MODE: receivers_params
val x = listOf("").filter {<# it: String #>
it.startsWith("")
}
@@ -0,0 +1,2 @@
// MODE: receivers_params
val x = listOf("").filter { it.startsWith(<hint text="prefix:" />"") }
@@ -0,0 +1,4 @@
// MODE: receivers_params
val x = buildString { <hint text="this: StringBuilder" />
append("foo")
}
+5
View File
@@ -0,0 +1,5 @@
// MODE: return
val x = run foo@{
println("foo")
1<# ^foo #>
}
@@ -0,0 +1,13 @@
// MODE: return
fun test() {
run {
val files: Any? = null
run@
12<# ^run #>
}
run {
val files: Any? = null
run@12<# ^run #>
}
}
+12
View File
@@ -0,0 +1,12 @@
// MODE: return
val x = run hello@{
if (true) {
}
run { // Two hints here
when (true) {
true -> 1<# ^run #>
false -> 0<# ^run #>
}
}<# ^hello #>
}
@@ -0,0 +1,4 @@
// MODE: return
val x = run {
1
}
+5
View File
@@ -0,0 +1,5 @@
// MODE: return
val x = run {
println(1)
if (true) 1 else { 0 }<# ^run #>
}
@@ -0,0 +1,13 @@
// MODE: return
fun bar() {
var test = 0
run {
test
test++<# ^run #>
}
run {
test
++test<# ^run #>
}
}
+5
View File
@@ -0,0 +1,5 @@
// MODE: return
val x = run {
var s = "abc"
s.length<# ^run #>
}
@@ -0,0 +1,5 @@
// MODE: return
fun test() = run {
val a = 1
{ a }<# ^run #>
}
+5
View File
@@ -0,0 +1,5 @@
// MODE: return
val x = run {
println("foo")
1<# ^run #>
}
+7
View File
@@ -0,0 +1,7 @@
// MODE: return
val x = run {
when (true) {
true -> 1<# ^run #>
false -> 0<# ^run #>
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2020 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.hints
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.LightProjectDescriptor
import com.intellij.testFramework.utils.inlays.InlayHintsProviderTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import java.io.File
@Suppress("UnstableApiUsage")
abstract class AbstractKotlinLambdasHintsProvider :
InlayHintsProviderTestCase() { // Abstract- prefix is just a convention for GenerateTests
override fun getProjectDescriptor(): LightProjectDescriptor {
return KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
}
fun doTest(testPath: String) { // named according to the convention imposed by GenerateTests
assertThatActualHintsMatch(testPath)
}
private fun assertThatActualHintsMatch(fileName: String) {
with(KotlinLambdasHintsProvider()) {
val fileContents = FileUtil.loadFile(File(fileName), true)
val settings = createSettings()
with(settings) {
when (InTextDirectivesUtils.findStringWithPrefixes(fileContents, "// MODE: ")) {
"return" -> set(returns = true)
"receivers_params" -> set(receiversAndParams = true)
"return-&-receivers_params" -> set(returns = true, receiversAndParams = true)
else -> set()
}
}
testProvider("KotlinLambdasHintsProvider.kt", fileContents, this, settings)
}
}
private fun KotlinLambdasHintsProvider.Settings.set(returns: Boolean = false, receiversAndParams: Boolean = false) {
this.returnExpressions = returns
this.implicitReceiversAndParams = receiversAndParams
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2010-2020 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.hints;
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/hints/lambda")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class KotlinLambdasHintsProviderGenerated extends AbstractKotlinLambdasHintsProvider {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInLambda() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/codeInsight/hints/lambda"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@TestMetadata("AnnotatedStatement.kt")
public void testAnnotatedStatement() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/AnnotatedStatement.kt");
}
@TestMetadata("DisabledHints.kt")
public void testDisabledHints() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/DisabledHints.kt");
}
@TestMetadata("Elvis.kt")
public void testElvis() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/Elvis.kt");
}
@TestMetadata("If.kt")
public void testIf() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/If.kt");
}
@TestMetadata("ImplicitIt.kt")
public void testImplicitIt() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/ImplicitIt.kt");
}
@TestMetadata("ImplicitSingleLine.kt")
public void testImplicitSingleLine() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/ImplicitSingleLine.kt");
}
@TestMetadata("ImplicitThis.kt")
public void testImplicitThis() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/ImplicitThis.kt");
}
@TestMetadata("Label.kt")
public void testLabel() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/Label.kt");
}
@TestMetadata("LabeledStatement.kt")
public void testLabeledStatement() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/LabeledStatement.kt");
}
@TestMetadata("Nested.kt")
public void testNested() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/Nested.kt");
}
@TestMetadata("NoHintForSingleExpression.kt")
public void testNoHintForSingleExpression() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/NoHintForSingleExpression.kt");
}
@TestMetadata("OneLineIf.kt")
public void testOneLineIf() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/OneLineIf.kt");
}
@TestMetadata("PostfixPrefixExpr.kt")
public void testPostfixPrefixExpr() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/PostfixPrefixExpr.kt");
}
@TestMetadata("Qualified.kt")
public void testQualified() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/Qualified.kt");
}
@TestMetadata("ReturnFunType.kt")
public void testReturnFunType() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/ReturnFunType.kt");
}
@TestMetadata("SimpleCase.kt")
public void testSimpleCase() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/SimpleCase.kt");
}
@TestMetadata("When.kt")
public void testWhen() throws Exception {
runTest("idea/testData/codeInsight/hints/lambda/When.kt");
}
}
@@ -1,41 +0,0 @@
/*
* 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.parameterInfo
import com.intellij.codeInsight.hints.HintInfo
import com.intellij.codeInsight.hints.InlayParameterHintsExtension
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.codeInsight.hints.HintType
import org.junit.Assert
internal fun JavaCodeInsightTestFixture.checkHintType(text: String, hintType: HintType) {
configureByText("A.kt", text.trimIndent())
doHighlighting()
checkHintType(hintType)
}
internal fun JavaCodeInsightTestFixture.checkHintType(hintType: HintType) {
val hintInfo = getHintInfoFromProvider(caretOffset, file, editor)
Assert.assertNotNull("No hint available at caret", hintInfo)
Assert.assertEquals(hintType.option.name, (hintInfo as HintInfo.OptionInfo).optionName)
}
// It's crucial for this method to be conformable with IDEA internals.
// Originally copied from com.intellij.codeInsight.hints.getHintInfoFromProvider()
private fun getHintInfoFromProvider(offset: Int, file: PsiFile, editor: Editor): HintInfo? {
val element = file.findElementAt(offset) ?: return null
val provider = InlayParameterHintsExtension.forLanguage(file.language) ?: return null
val isHintOwnedByElement: (PsiElement) -> Boolean = { e -> provider.getHintInfo(e)?.isOwnedByPsiElement(e, editor) ?: false }
val method = PsiTreeUtil.findFirstParent(element, isHintOwnedByElement) ?: return null
return provider.getHintInfo(method)
}
@@ -1,59 +0,0 @@
/*
* 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.parameterInfo
import org.jetbrains.kotlin.idea.codeInsight.hints.HintType
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
import org.junit.runner.RunWith
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
class LambdaImplicitHintsTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getProjectDescriptor(): KotlinLightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
fun check(text: String) {
myFixture.configureByText("A.kt", text)
myFixture.testInlays()
}
fun testHintType() {
myFixture.checkHintType(
"""
val x = listOf("").filter { <caret>
}
""",
HintType.LAMBDA_IMPLICIT_PARAMETER_RECEIVER
)
}
fun testSimpleIt() {
check(
"""
val x = listOf("").filter {<hint text="it: String" />
it.startsWith(<hint text="prefix:" />"")
}"""
)
}
fun testSimpleThis() {
check(
"""
val x = buildString {<hint text="this: StringBuilder" />
append("foo")
}"""
)
}
fun testSingleLine() {
check(
"""
val x = listOf("").filter { it.startsWith(<hint text="prefix:" />"") }
"""
)
}
}
@@ -1,338 +0,0 @@
/*
* 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.parameterInfo
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LineExtensionInfo
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.testFramework.utils.inlays.InlayHintsChecker
import org.jetbrains.kotlin.idea.codeInsight.hints.HintType
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
import org.jetbrains.kotlin.test.TagsTestDataUtil
import org.junit.Assert
import org.junit.runner.RunWith
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
class LambdaReturnValueHintsTest : KotlinLightCodeInsightFixtureTestCase() {
companion object {
const val DISABLE_ACTION_TEXT = "Do not show lambda return expression hints"
const val ENABLE_ACTION_TEXT = "Show lambda return expression hints"
}
override fun getProjectDescriptor(): KotlinLightProjectDescriptor = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
private class LineExtensionInfoTag(offset: Int, data: LineExtensionInfo) :
TagsTestDataUtil.TagInfo<LineExtensionInfo>(offset, true, true, false, data) {
override fun getName() = "hint"
override fun getAttributesString(): String = "text=\"${data.text}\""
}
private class CaretTag(editor: Editor) :
TagsTestDataUtil.TagInfo<Any>(
editor.caretModel.currentCaret.offset,
/*isStart = */true, /*isClosed = */false, /*isFixed = */true,
"caret"
)
private fun collectActualLineExtensionsTags(): List<LineExtensionInfoTag> {
val tags = ArrayList<LineExtensionInfoTag>()
val lineCount = myFixture.editor.document.lineCount
for (i in 0 until lineCount) {
val lineEndOffset = myFixture.editor.document.getLineEndOffset(i)
(myFixture.editor as EditorImpl).processLineExtensions(i) { lineExtensionInfo ->
tags.add(LineExtensionInfoTag(lineEndOffset, lineExtensionInfo))
true
}
}
return tags
}
fun check(text: String) {
myFixture.configureByText("A.kt", text.trimIndent())
val expectedText = run {
val tags = if (editor.caretModel.offset > 0) listOf(CaretTag(editor)) else emptyList()
TagsTestDataUtil.insertTagsInText(tags, editor.document.text) { null }
}
// Clean test file from the hints tags
InlayHintsChecker(myFixture).extractInlaysAndCaretInfo(editor.document)
myFixture.doHighlighting()
Assert.assertTrue(
"No other inlays should be present in the file",
editor.inlayModel.getInlineElementsInRange(0, editor.document.textLength).isEmpty()
)
if (editor.caretModel.offset > 0) {
val availableIntentions = myFixture.availableIntentions
Assert.assertTrue(
"Disable action with text `$DISABLE_ACTION_TEXT` is expected: \n${availableIntentions.joinToString(separator = "\n") { " $it" }}",
availableIntentions.any { it.text == DISABLE_ACTION_TEXT }
)
}
val actualText = run {
val tags = ArrayList<TagsTestDataUtil.TagInfo<*>>()
if (editor.caretModel.offset > 0) {
tags.add(CaretTag(editor))
}
tags.addAll(collectActualLineExtensionsTags())
TagsTestDataUtil.insertTagsInText(tags, editor.document.text) { null }
}
Assert.assertEquals(expectedText, actualText)
}
fun testDisableEnableActions() {
myFixture.configureByText(
"A.kt",
"""
val x = run {
println("foo")
1<caret>
}
""".trimIndent()
)
Assert.assertTrue("Return expression hint should be enabled", HintType.LAMBDA_RETURN_EXPRESSION.enabled)
try {
val disableIntention = findDisableReturnHintsIntention()
disableIntention.invoke(project, editor, file)
Assert.assertFalse("Disable action doesn't work", HintType.LAMBDA_RETURN_EXPRESSION.option.get())
val availableIntentions = myFixture.availableIntentions
Assert.assertTrue(
intentionsPresenceErrorMessage(
"Disable action shouldn't be present when option is already disabled", availableIntentions
),
availableIntentions.find { it.text == DISABLE_ACTION_TEXT } == null
)
val enableAction = availableIntentions.find { it.text == ENABLE_ACTION_TEXT }
Assert.assertTrue(
intentionsPresenceErrorMessage("No enable action with text $ENABLE_ACTION_TEXT found", availableIntentions),
enableAction != null
)
enableAction!!.invoke(project, editor, file)
Assert.assertTrue("Enable action doesn't work", HintType.LAMBDA_RETURN_EXPRESSION.option.get())
} finally {
HintType.LAMBDA_RETURN_EXPRESSION.option.set(true)
}
}
private fun findDisableReturnHintsIntention(): IntentionAction {
val availableIntentions = myFixture.availableIntentions
return availableIntentions.find { it.text == DISABLE_ACTION_TEXT }
?: throw AssertionError(
intentionsPresenceErrorMessage("Disable action with text `$DISABLE_ACTION_TEXT` is expected", availableIntentions)
)
}
private fun intentionsPresenceErrorMessage(message: String, intentions: List<IntentionAction>): String {
return "$message: \n" +
intentions.joinToString(separator = "\n") { " $it" }
}
fun testSimple() {
check(
"""
val x = run {
println("foo")
1<caret><hint text=" "/><hint text="^run"/>
}
"""
)
}
fun testQualified() {
check(
"""
val x = run {
var s = "abc"
s.length<caret><hint text=" "/><hint text="^run"/>
}
"""
)
}
fun testIf() {
check(
"""
val x = run {
if (true) {
1<hint text=" "/><hint text="^run"/>
} else {
0<hint text=" "/><hint text="^run"/>
}
}
"""
)
}
fun testOneLineIf() {
check(
"""
val x = run {
println(1)
if (true) 1 else { 0 }<caret><hint text=" "/><hint text="^run"/>
}
"""
)
}
fun testWhen() {
check(
"""
val x = run {
when (true) {
true -> 1<hint text=" "/><hint text="^run"/>
false -> 0<hint text=" "/><hint text="^run"/>
}
}
"""
)
}
fun testNoHintForSingleExpression() {
check(
"""
val x = run {
1
}
"""
)
}
fun testLabel() {
check(
"""
val x = run foo@{
println("foo")
1<hint text=" "/><hint text="^foo"/>
}
"""
)
}
fun testNested() {
check(
"""
val x = run hello@{
if (true) {
}
run { // Two hints here
when (true) {
true -> 1<hint text=" "/><hint text="^run"/>
false -> 0<hint text=" "/><hint text="^run"/>
}
}<hint text=" "/><hint text="^hello"/>
}
"""
)
}
fun testElvisOperator() {
check(
"""
fun foo() {
run {
val length: Int? = null
length ?: 0<hint text=" "/><hint text="^run"/>
}
}
"""
)
}
fun testPostfixPrefixExpressions() {
check(
"""
fun bar() {
var test = 0
run {
test
test++<hint text=" "/><hint text="^run"/>
}
run {
test
++test<hint text=" "/><hint text="^run"/>
}
}
"""
)
}
fun testAnnotatedStatement() {
check(
"""
@Target(AnnotationTarget.EXPRESSION)
annotation class Some
fun test() {
run {
val files: Any? = null
@Some
12<hint text=" "/><hint text="^run"/>
}
run {
val files: Any? = null
@Some 12<hint text=" "/><hint text="^run"/>
}
}
"""
)
}
fun testLabeledStatement() {
check(
"""
fun test() {
run {
val files: Any? = null
run@
12<hint text=" "/><hint text="^run"/>
}
run {
val files: Any? = null
run@12<hint text=" "/><hint text="^run"/>
}
}
"""
)
}
fun testReturnFunctionType() {
check(
"""
fun test() = run {
val a = 1
{ a }<hint text=" "/><hint text="^run"/>
}
"""
)
}
}