diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinFoldingBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinFoldingBuilder.kt index 57305a48c75..6f360f660f1 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinFoldingBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinFoldingBuilder.kt @@ -28,11 +28,11 @@ import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.KtNodeTypes -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.PackageViewDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.analyze -import org.jetbrains.kotlin.idea.imports.importableFqName -import org.jetbrains.kotlin.idea.references.KtReference +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.editor.fixers.endLine +import org.jetbrains.kotlin.idea.editor.fixers.startLine +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.references.resolveMainReferenceToDescriptors import org.jetbrains.kotlin.kdoc.lexer.KDocTokens import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName @@ -41,49 +41,22 @@ import org.jetbrains.kotlin.psi.psiUtil.allChildren import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.referenceExpression import org.jetbrains.kotlin.psi.psiUtil.startOffset -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor +import org.jetbrains.kotlin.resolve.calls.components.isVararg class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware { - private val collectionFactoryFunctions: Set = + private val collectionFactoryFunctionsNames: Set = setOf( - FqName("kotlin.arrayOf"), - FqName("kotlin.booleanArrayOf"), - FqName("kotlin.byteArrayOf"), - FqName("kotlin.charArrayOf"), - FqName("kotlin.doubleArrayOf"), - FqName("kotlin.floatArrayOf"), - FqName("kotlin.intArrayOf"), - FqName("kotlin.longArrayOf"), - FqName("kotlin.shortArrayOf"), - FqName("kotlin.kotlin_builtins.arrayOf"), - FqName("kotlin.kotlin_builtins.booleanArrayOf"), - FqName("kotlin.kotlin_builtins.byteArrayOf"), - FqName("kotlin.kotlin_builtins.charArrayOf"), - FqName("kotlin.kotlin_builtins.doubleArrayOf"), - FqName("kotlin.kotlin_builtins.floatArrayOf"), - FqName("kotlin.kotlin_builtins.intArrayOf"), - FqName("kotlin.kotlin_builtins.longArrayOf"), - FqName("kotlin.kotlin_builtins.shortArrayOf"), - FqName("kotlin.collections.arrayListOf"), - FqName("kotlin.collections.hashMapOf"), - FqName("kotlin.collections.hashSetOf"), - FqName("kotlin.collections.linkedMapOf"), - FqName("kotlin.collections.linkedSetOf"), - FqName("kotlin.collections.linkedStringMapOf"), - FqName("kotlin.collections.linkedStringSetOf"), - FqName("kotlin.collections.listOf"), - FqName("kotlin.collections.listOfNotNull"), - FqName("kotlin.collections.mapOf"), - FqName("kotlin.collections.mutableListOf"), - FqName("kotlin.collections.mutableMapOf"), - FqName("kotlin.collections.mutableSetOf"), - FqName("kotlin.collections.setOf"), - FqName("kotlin.collections.sortedMapOf"), - FqName("kotlin.collections.sortedSetOf"), - FqName("kotlin.collections.stringMapOf"), - FqName("kotlin.collections.stringSetOf") + "arrayOf", "booleanArrayOf", "byteArrayOf", "charArrayOf", "doubleArrayOf", + "floatArrayOf", "intArrayOf", "longArrayOf", "shortArrayOf", "arrayListOf", + "hashMapOf", "hashSetOf", + "linkedMapOf", "linkedSetOf", "linkedStringMapOf", "linkedStringSetOf", + "listOf", "listOfNotNull", + "mapOf", + "mutableListOf", "mutableMapOf", "mutableSetOf", + "setOf", + "sortedMapOf", "sortedSetOf", + "stringMapOf", "stringSetOf" ) override fun buildLanguageFoldRegions( @@ -111,7 +84,7 @@ class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware { } private fun appendDescriptors(node: ASTNode, document: Document, descriptors: MutableList) { - if (needFolding(node)) { + if (needFolding(node, document)) { val textRange = getRangeToFold(node) val relativeRange = textRange.shiftRight(-node.textRange.startOffset) val foldRegionText = node.chars.subSequence(relativeRange.startOffset, relativeRange.endOffset) @@ -127,7 +100,7 @@ class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware { } } - private fun needFolding(node: ASTNode): Boolean { + private fun needFolding(node: ASTNode, document: Document): Boolean { val type = node.elementType val parentType = node.treeParent?.elementType @@ -135,37 +108,26 @@ class KotlinFoldingBuilder : CustomFoldingBuilder(), DumbAware { (type == KtNodeTypes.BLOCK && parentType != KtNodeTypes.FUNCTION_LITERAL) || type == KtNodeTypes.CLASS_BODY || type == KtTokens.BLOCK_COMMENT || type == KDocTokens.KDOC || type == KtNodeTypes.STRING_TEMPLATE || type == KtNodeTypes.PRIMARY_CONSTRUCTOR || - node.shouldFoldCollection() + node.shouldFoldCollection(document) } - private fun ASTNode.shouldFoldCollection(): Boolean = with((psi as? KtCallExpression)?.referenceExpression()) { - if (this == null || DumbService.isDumb(project)) { + private fun ASTNode.shouldFoldCollection(document: Document): Boolean { + val call = psi as? KtCallExpression ?: return false + if (DumbService.isDumb(call.project)) return false + + if (call.valueArguments.size < 2) return false + + // Similar check will be done latter, but we still use it here to avoid unnecessary resolve. + if (call.startLine(document) == call.endLine(document)) return false + + val reference = call.referenceExpression() ?: return false + if (reference.mainReference.resolvesByNames.any { name -> name.isSpecial || name.identifier !in collectionFactoryFunctionsNames }) { return false } - fun KtReference.targets(bindingContext: BindingContext): Collection = - bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, element as? KtReferenceExpression]?.let { listOf(it) } - ?: resolveToDescriptors(bindingContext) - - for (reference in references) { - if (reference !is KtReference) continue - - val names = reference.resolvesByNames - val bindingContext = analyze() - val targets = reference.targets(bindingContext) - - for (target in targets) { - val importableDescriptor = target.getImportableDescriptor() - if (importableDescriptor.name !in names) continue // resolved via alias - - val importableFqName = target.importableFqName ?: continue - if (target !is PackageViewDescriptor && importableFqName in collectionFactoryFunctions) { - return true - } - } - } - - return false + // Do all possible psi checks before actual resolve + val functionDescriptor = reference.resolveMainReferenceToDescriptors().singleOrNull() as? FunctionDescriptor ?: return false + return functionDescriptor.valueParameters.size == 1 && functionDescriptor.valueParameters.first().isVararg } private fun getRangeToFold(node: ASTNode): TextRange { diff --git a/idea/src/org/jetbrains/kotlin/idea/editor/fixers/fixersUtil.kt b/idea/src/org/jetbrains/kotlin/idea/editor/fixers/fixersUtil.kt index 21a7a4dd95f..e780a41e4bf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/editor/fixers/fixersUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/editor/fixers/fixersUtil.kt @@ -25,4 +25,5 @@ val TextRange.start: Int get() = startOffset val TextRange.end: Int get() = endOffset fun PsiElement.startLine(doc: Document): Int = doc.getLineNumber(range.start) +fun PsiElement.endLine(doc: Document): Int = doc.getLineNumber(range.end) fun PsiElement?.isWithCaret(caret: Int) = this?.textRange?.contains(caret) == true diff --git a/idea/testData/folding/checkCollapse/collectionFactoryFunctions.kt b/idea/testData/folding/checkCollapse/collectionFactoryFunctions.kt index 44065ef81a4..1cf1306dd2a 100644 --- a/idea/testData/folding/checkCollapse/collectionFactoryFunctions.kt +++ b/idea/testData/folding/checkCollapse/collectionFactoryFunctions.kt @@ -1,12 +1,17 @@ -val array = arrayOf ( +val array = arrayOf( 11 to 0, 12 to 1, 13 to 3, -14 to 4, -15 to 5, -16 to 6, -17 to 7, -18 to 8, -19 to 9, -20 to 10 -) \ No newline at end of file +) + +val set = setOf( + 1, + 2 +) + +val list = listOf( + 1, + 2 +) + +// WITH_RUNTIME \ No newline at end of file diff --git a/idea/testData/folding/checkCollapse/collectionFactoryFunctionsEmptyOneLine.kt b/idea/testData/folding/checkCollapse/collectionFactoryFunctionsEmptyOneLine.kt new file mode 100644 index 00000000000..bba9538a13b --- /dev/null +++ b/idea/testData/folding/checkCollapse/collectionFactoryFunctionsEmptyOneLine.kt @@ -0,0 +1 @@ +val array = arrayOf(11 to 0, 12 to 1, 13 to 3, 14 to 4, 15 to 5, 16 to 6, 17 to 7) \ No newline at end of file diff --git a/idea/testData/folding/checkCollapse/collectionFactoryFunctionsFewArguments.kt b/idea/testData/folding/checkCollapse/collectionFactoryFunctionsFewArguments.kt new file mode 100644 index 00000000000..04c17099daa --- /dev/null +++ b/idea/testData/folding/checkCollapse/collectionFactoryFunctionsFewArguments.kt @@ -0,0 +1,5 @@ +val arrayEmpty = arrayOf() +val arrayOneOneLiner = arrayOf(1) +val arrayOneMultiLine = arrayOf( + 1 +) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java b/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java index e2006ff13d3..9e33ade96ad 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java @@ -21,12 +21,10 @@ import com.intellij.codeInsight.folding.impl.JavaCodeFoldingSettingsImpl; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCaseBase; -import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor; +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase; import org.jetbrains.kotlin.test.SettingsConfigurator; import org.junit.Assert; @@ -35,7 +33,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.function.Consumer; -public abstract class AbstractKotlinFoldingTest extends KotlinLightCodeInsightFixtureTestCaseBase { +public abstract class AbstractKotlinFoldingTest extends KotlinLightCodeInsightFixtureTestCase { protected void doTest(@NotNull String path) { myFixture.testFolding(path); } @@ -111,10 +109,4 @@ public abstract class AbstractKotlinFoldingTest extends KotlinLightCodeInsightFi Assert.assertEquals(expectedContent, actual); } - - @NotNull - @Override - protected LightProjectDescriptor getProjectDescriptor() { - return KotlinLightProjectDescriptor.INSTANCE; - } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java.182 b/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java.182 index bc2aa03db2c..05494e802d3 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java.182 +++ b/idea/tests/org/jetbrains/kotlin/idea/folding/AbstractKotlinFoldingTest.java.182 @@ -21,11 +21,10 @@ import com.intellij.codeInsight.folding.impl.JavaCodeFoldingSettingsImpl; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.testFramework.LightProjectDescriptor; +import com.intellij.testFramework.PlatformTestCase; import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl; import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCaseBase; -import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor; +import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase; import org.jetbrains.kotlin.test.SettingsConfigurator; import org.junit.Assert; @@ -34,7 +33,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.function.Consumer; -public abstract class AbstractKotlinFoldingTest extends KotlinLightCodeInsightFixtureTestCaseBase { +public abstract class AbstractKotlinFoldingTest extends KotlinLightCodeInsightFixtureTestCase { protected void doTest(@NotNull String path) { myFixture.testFolding(path); } @@ -110,10 +109,4 @@ public abstract class AbstractKotlinFoldingTest extends KotlinLightCodeInsightFi Assert.assertEquals(expectedContent, actual); } - - @NotNull - @Override - protected LightProjectDescriptor getProjectDescriptor() { - return KotlinLightProjectDescriptor.INSTANCE; - } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/folding/KotlinFoldingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/folding/KotlinFoldingTestGenerated.java index 027362ec5b2..cf27d2d838c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/folding/KotlinFoldingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/folding/KotlinFoldingTestGenerated.java @@ -89,6 +89,16 @@ public class KotlinFoldingTestGenerated extends AbstractKotlinFoldingTest { runTest("idea/testData/folding/checkCollapse/collectionFactoryFunctions.kt"); } + @TestMetadata("collectionFactoryFunctionsEmptyOneLine.kt") + public void testCollectionFactoryFunctionsEmptyOneLine() throws Exception { + runTest("idea/testData/folding/checkCollapse/collectionFactoryFunctionsEmptyOneLine.kt"); + } + + @TestMetadata("collectionFactoryFunctionsFewArguments.kt") + public void testCollectionFactoryFunctionsFewArguments() throws Exception { + runTest("idea/testData/folding/checkCollapse/collectionFactoryFunctionsFewArguments.kt"); + } + @TestMetadata("customRegions.kt") public void testCustomRegions() throws Exception { runTest("idea/testData/folding/checkCollapse/customRegions.kt");