From 346df07adcd052c222aba46ea492ef313574e0f1 Mon Sep 17 00:00:00 2001 From: Florian Kistner Date: Mon, 6 Jul 2020 16:09:26 +0200 Subject: [PATCH] 203: Fix compilation for 203 --- .../KotlinCoreApplicationEnvironment.java.203 | 55 + .../kotlin/psi/KtElementImplStub.java.203 | 122 ++ ...tAnnotationUseSiteTargetElementType.kt.203 | 35 + .../elements/KtImportAliasElementType.kt.203 | 31 + .../KtModifierListElementType.java.203 | 44 + .../KtPlaceHolderStubElementType.java.203 | 40 + .../stubs/elements/KtScriptElementType.kt.203 | 39 + .../stubs/elements/KtStubElementType.java.203 | 108 ++ .../KtValueArgumentElementType.kt.203 | 31 + .../impl/KotlinImportAliasStubImpl.kt.203 | 20 + .../testFramework/KtUsefulTestCase.java.203 | 1178 ++++++++++++ .../test/KotlinCompletionTestCase.java.203 | 37 + .../test/KotlinCodeInsightTestCase.java.203 | 37 + ...ghtCodeInsightFixtureTestCaseBase.java.203 | 99 + .../idea/actions/NewKotlinFileAction.kt.203 | 263 +++ ...bstractConfigureKotlinInTempDirTest.kt.203 | 38 + .../AbstractConfigureKotlinTest.kt.203 | 256 +++ .../quickDoc/QuickDocInHierarchyTest.kt.203 | 50 + .../kotlin/idea/perf/UltraLightChecker.kt.203 | 336 ++++ ...estedRefactoringChangeCollectorTest.kt.203 | 207 +++ ...gestedRefactoringChangeListenerTest.kt.203 | 401 +++++ .../KotlinSuggestedRefactoringTest.kt.203 | 1589 +++++++++++++++++ .../AbstractScriptConfigurationTest.kt.203 | 373 ++++ 23 files changed, 5389 insertions(+) create mode 100644 compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.java.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/KtElementImplStub.java.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtAnnotationUseSiteTargetElementType.kt.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtImportAliasElementType.kt.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtModifierListElementType.java.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtPlaceHolderStubElementType.java.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtScriptElementType.kt.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtStubElementType.java.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtValueArgumentElementType.kt.203 create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/stubs/impl/KotlinImportAliasStubImpl.kt.203 create mode 100644 compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java.203 create mode 100644 idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KotlinCompletionTestCase.java.203 create mode 100644 idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinCodeInsightTestCase.java.203 create mode 100644 idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCaseBase.java.203 create mode 100644 idea/src/org/jetbrains/kotlin/idea/actions/NewKotlinFileAction.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinInTempDirTest.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinTest.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/editor/quickDoc/QuickDocInHierarchyTest.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.203 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationTest.kt.203 diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.java.203 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.java.203 new file mode 100644 index 00000000000..692e0995d7a --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreApplicationEnvironment.java.203 @@ -0,0 +1,55 @@ +/* + * 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.cli.jvm.compiler; + +import com.intellij.DynamicBundle; +import com.intellij.codeInsight.ContainerProvider; +import com.intellij.codeInsight.runner.JavaMainMethodProvider; +import com.intellij.core.JavaCoreApplicationEnvironment; +import com.intellij.lang.MetaLanguage; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.vfs.VirtualFileSystem; +import com.intellij.psi.FileContextProvider; +import com.intellij.psi.augment.PsiAugmentProvider; +import com.intellij.psi.compiled.ClassFileDecompilers; +import com.intellij.psi.meta.MetaDataContributor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem; + +public class KotlinCoreApplicationEnvironment extends JavaCoreApplicationEnvironment { + public static KotlinCoreApplicationEnvironment create(@NotNull Disposable parentDisposable, boolean unitTestMode) { + KotlinCoreApplicationEnvironment environment = new KotlinCoreApplicationEnvironment(parentDisposable, unitTestMode); + registerExtensionPoints(); + return environment; + } + + private KotlinCoreApplicationEnvironment(@NotNull Disposable parentDisposable, boolean unitTestMode) { + super(parentDisposable, unitTestMode); + } + + private static void registerExtensionPoints() { + registerApplicationExtensionPoint(DynamicBundle.LanguageBundleEP.EP_NAME, DynamicBundle.LanguageBundleEP.class); + registerApplicationExtensionPoint(FileContextProvider.EP_NAME, FileContextProvider.class); + + registerApplicationExtensionPoint(MetaDataContributor.EP_NAME, MetaDataContributor.class); + registerApplicationExtensionPoint(PsiAugmentProvider.EP_NAME, PsiAugmentProvider.class); + registerApplicationExtensionPoint(JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider.class); + + registerApplicationExtensionPoint(ContainerProvider.EP_NAME, ContainerProvider.class); + registerApplicationExtensionPoint(ClassFileDecompilers.getInstance().EP_NAME, ClassFileDecompilers.Decompiler.class); + + registerApplicationExtensionPoint(MetaLanguage.EP_NAME, MetaLanguage.class); + + IdeaExtensionPoints.INSTANCE.registerVersionSpecificAppExtensionPoints(Extensions.getRootArea()); + } + + @Nullable + @Override + protected VirtualFileSystem createJrtFileSystem() { + return new CoreJrtFileSystem(); + } +} \ No newline at end of file diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtElementImplStub.java.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/KtElementImplStub.java.203 new file mode 100644 index 00000000000..1ccc116d1e0 --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtElementImplStub.java.203 @@ -0,0 +1,122 @@ +/* + * 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.psi; + +import com.intellij.extapi.psi.StubBasedPsiElementBase; +import com.intellij.lang.ASTNode; +import com.intellij.lang.Language; +import com.intellij.psi.*; +import com.intellij.psi.stubs.IStubElementType; +import com.intellij.psi.stubs.StubElement; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.idea.KotlinLanguage; +import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt; +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementType; + +import java.util.Arrays; +import java.util.List; + +public class KtElementImplStub> extends StubBasedPsiElementBase + implements KtElement, StubBasedPsiElement { + public KtElementImplStub(@NotNull T stub, @NotNull IStubElementType nodeType) { + super(stub, nodeType); + } + + public KtElementImplStub(@NotNull ASTNode node) { + super(node); + } + + @NotNull + @Override + public Language getLanguage() { + return KotlinLanguage.INSTANCE; + } + + @Override + public String toString() { + return getElementType().toString(); + } + + @Override + @SuppressWarnings("unchecked") + public final void accept(@NotNull PsiElementVisitor visitor) { + if (visitor instanceof KtVisitor) { + accept((KtVisitor) visitor, null); + } + else { + visitor.visitElement(this); + } + } + + @NotNull + @Override + public KtFile getContainingKtFile() { + PsiFile file = getContainingFile(); + if (!(file instanceof KtFile)) { + // KtElementImpl.copy() might be the reason for this exception + String fileString = file.isValid() ? (" " + file.getText()) : ""; + throw new IllegalStateException("KtElement not inside KtFile: " + file + fileString + + " for element " + this + " of type " + this.getClass() + " node = " + getNode()); + } + return (KtFile) file; + } + + @Override + public void acceptChildren(@NotNull KtVisitor visitor, D data) { + PsiElement child = getFirstChild(); + while (child != null) { + if (child instanceof KtElement) { + ((KtElement) child).accept(visitor, data); + } + child = child.getNextSibling(); + } + } + + @Override + public R accept(@NotNull KtVisitor visitor, D data) { + return visitor.visitKtElement(this, data); + } + + @Override + public void delete() throws IncorrectOperationException { + KtElementUtilsKt.deleteSemicolon(this); + super.delete(); + } + + @Override + @SuppressWarnings("deprecation") + public PsiReference getReference() { + PsiReference[] references = getReferences(); + if (references.length == 1) return references[0]; + else return null; + } + + @NotNull + @Override + public PsiReference[] getReferences() { + return KotlinReferenceProvidersService.getReferencesFromProviders(this); + } + + @NotNull + protected , StubT extends StubElement> List getStubOrPsiChildrenAsList( + @NotNull KtStubElementType elementType + ) { + return Arrays.asList(getStubOrPsiChildren(elementType, elementType.getArrayFactory())); + } + + @NotNull + @Override + public KtElement getPsiOrParent() { + return this; + } + + @Override + public PsiElement getParent() { + PsiElement substitute = KtPsiUtilKt.getParentSubstitute(this); + return substitute != null ? substitute : super.getParent(); + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtAnnotationUseSiteTargetElementType.kt.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtAnnotationUseSiteTargetElementType.kt.203 new file mode 100644 index 00000000000..49c92c5370c --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtAnnotationUseSiteTargetElementType.kt.203 @@ -0,0 +1,35 @@ +/* + * 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.psi.stubs.elements + +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement +import com.intellij.psi.stubs.StubInputStream +import com.intellij.psi.stubs.StubOutputStream +import com.intellij.util.io.StringRef +import org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget +import org.jetbrains.kotlin.psi.stubs.KotlinAnnotationUseSiteTargetStub +import org.jetbrains.kotlin.psi.stubs.impl.KotlinAnnotationUseSiteTargetStubImpl + +class KtAnnotationUseSiteTargetElementType(debugName: String) : + KtStubElementType( + debugName, KtAnnotationUseSiteTarget::class.java, KotlinAnnotationUseSiteTargetStub::class.java + ) { + + override fun createStub(psi: KtAnnotationUseSiteTarget, parentStub: StubElement): KotlinAnnotationUseSiteTargetStub { + val useSiteTarget = psi.getAnnotationUseSiteTarget().name + return KotlinAnnotationUseSiteTargetStubImpl(parentStub, StringRef.fromString(useSiteTarget)!!) + } + + override fun serialize(stub: KotlinAnnotationUseSiteTargetStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.getUseSiteTarget()) + } + + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement): KotlinAnnotationUseSiteTargetStub { + val useSiteTarget = dataStream.readName() + return KotlinAnnotationUseSiteTargetStubImpl(parentStub, useSiteTarget!!) + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtImportAliasElementType.kt.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtImportAliasElementType.kt.203 new file mode 100644 index 00000000000..5c363038ff3 --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtImportAliasElementType.kt.203 @@ -0,0 +1,31 @@ +/* + * 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.psi.stubs.elements + +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement +import com.intellij.psi.stubs.StubInputStream +import com.intellij.psi.stubs.StubOutputStream +import com.intellij.util.io.StringRef +import org.jetbrains.kotlin.psi.KtImportAlias +import org.jetbrains.kotlin.psi.stubs.KotlinImportAliasStub +import org.jetbrains.kotlin.psi.stubs.impl.KotlinImportAliasStubImpl + +class KtImportAliasElementType(debugName: String) : + KtStubElementType(debugName, KtImportAlias::class.java, KotlinImportAliasStub::class.java) { + override fun createStub(psi: KtImportAlias, parentStub: StubElement?): KotlinImportAliasStub { + return KotlinImportAliasStubImpl(parentStub, StringRef.fromString(psi.name)) + } + + override fun serialize(stub: KotlinImportAliasStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.getName()) + } + + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement?): KotlinImportAliasStub { + val name = dataStream.readName() + return KotlinImportAliasStubImpl(parentStub, name) + } +} \ No newline at end of file diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtModifierListElementType.java.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtModifierListElementType.java.203 new file mode 100644 index 00000000000..321d238340a --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtModifierListElementType.java.203 @@ -0,0 +1,44 @@ +/* + * 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.psi.stubs.elements; + +import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.stubs.StubInputStream; +import com.intellij.psi.stubs.StubOutputStream; +import com.intellij.util.io.DataInputOutputUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.psi.KtModifierList; +import org.jetbrains.kotlin.psi.stubs.KotlinModifierListStub; +import org.jetbrains.kotlin.psi.stubs.impl.KotlinModifierListStubImpl; + +import java.io.IOException; + +import static org.jetbrains.kotlin.psi.stubs.impl.ModifierMaskUtils.computeMaskFromModifierList; + +public class KtModifierListElementType extends KtStubElementType { + public KtModifierListElementType(@NotNull @NonNls String debugName, @NotNull Class psiClass) { + super(debugName, psiClass, KotlinModifierListStub.class); + } + + @Override + public KotlinModifierListStub createStub(@NotNull T psi, StubElement parentStub) { + return new KotlinModifierListStubImpl(parentStub, computeMaskFromModifierList(psi), this); + } + + @Override + public void serialize(@NotNull KotlinModifierListStub stub, @NotNull StubOutputStream dataStream) throws IOException { + long mask = ((KotlinModifierListStubImpl) stub).getMask(); + DataInputOutputUtil.writeLONG(dataStream, mask); + } + + @NotNull + @Override + public KotlinModifierListStub deserialize(@NotNull StubInputStream dataStream, StubElement parentStub) throws IOException { + long mask = DataInputOutputUtil.readLONG(dataStream); + return new KotlinModifierListStubImpl(parentStub, mask, this); + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtPlaceHolderStubElementType.java.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtPlaceHolderStubElementType.java.203 new file mode 100644 index 00000000000..3abeed91ec9 --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtPlaceHolderStubElementType.java.203 @@ -0,0 +1,40 @@ +/* + * 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.psi.stubs.elements; + +import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.stubs.StubInputStream; +import com.intellij.psi.stubs.StubOutputStream; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.psi.KtElementImplStub; +import org.jetbrains.kotlin.psi.stubs.KotlinPlaceHolderStub; +import org.jetbrains.kotlin.psi.stubs.impl.KotlinPlaceHolderStubImpl; + +import java.io.IOException; + +public class KtPlaceHolderStubElementType>> extends + KtStubElementType, T> { + public KtPlaceHolderStubElementType(@NotNull @NonNls String debugName, @NotNull Class psiClass) { + super(debugName, psiClass, KotlinPlaceHolderStub.class); + } + + @Override + public KotlinPlaceHolderStub createStub(@NotNull T psi, StubElement parentStub) { + return new KotlinPlaceHolderStubImpl<>(parentStub, this); + } + + @Override + public void serialize(@NotNull KotlinPlaceHolderStub stub, @NotNull StubOutputStream dataStream) throws IOException { + //do nothing + } + + @NotNull + @Override + public KotlinPlaceHolderStub deserialize(@NotNull StubInputStream dataStream, StubElement parentStub) throws IOException { + return new KotlinPlaceHolderStubImpl<>(parentStub, this); + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtScriptElementType.kt.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtScriptElementType.kt.203 new file mode 100644 index 00000000000..15c241ddfcd --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtScriptElementType.kt.203 @@ -0,0 +1,39 @@ +/* + * 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.psi.stubs.elements + +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.IndexSink +import com.intellij.psi.stubs.StubElement +import com.intellij.psi.stubs.StubInputStream +import com.intellij.psi.stubs.StubOutputStream +import com.intellij.util.io.StringRef +import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.psi.stubs.KotlinScriptStub +import org.jetbrains.kotlin.psi.stubs.impl.KotlinScriptStubImpl + +class KtScriptElementType(debugName: String) : KtStubElementType( + debugName, KtScript::class.java, KotlinScriptStub::class.java +) { + + override fun createStub(psi: KtScript, parentStub: StubElement): KotlinScriptStub { + return KotlinScriptStubImpl(parentStub, StringRef.fromString(psi.fqName.asString())) + } + + override fun serialize(stub: KotlinScriptStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.getFqName().asString()) + } + + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement): KotlinScriptStub { + val fqName = dataStream.readName() + return KotlinScriptStubImpl(parentStub, fqName) + } + + + override fun indexStub(stub: KotlinScriptStub, sink: IndexSink) { + StubIndexService.getInstance().indexScript(stub, sink) + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtStubElementType.java.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtStubElementType.java.203 new file mode 100644 index 00000000000..a67feb9aa5f --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtStubElementType.java.203 @@ -0,0 +1,108 @@ +/* + * 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.psi.stubs.elements; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.intellij.psi.stubs.IStubElementType; +import com.intellij.psi.stubs.IndexSink; +import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.tree.IStubFileElementType; +import com.intellij.util.ArrayFactory; +import com.intellij.util.ReflectionUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.idea.KotlinLanguage; +import org.jetbrains.kotlin.psi.KtClassOrObject; +import org.jetbrains.kotlin.psi.KtElementImplStub; +import org.jetbrains.kotlin.psi.KtFunction; +import org.jetbrains.kotlin.psi.KtProperty; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; + +public abstract class KtStubElementType, PsiT extends KtElementImplStub> extends IStubElementType { + + @NotNull + private final Constructor byNodeConstructor; + @NotNull + private final Constructor byStubConstructor; + @NotNull + private final PsiT[] emptyArray; + @NotNull + private final ArrayFactory arrayFactory; + + @SuppressWarnings("unchecked") + public KtStubElementType(@NotNull @NonNls String debugName, @NotNull Class psiClass, @NotNull Class stubClass) { + super(debugName, KotlinLanguage.INSTANCE); + try { + byNodeConstructor = psiClass.getConstructor(ASTNode.class); + byStubConstructor = psiClass.getConstructor(stubClass); + } + catch (NoSuchMethodException e) { + throw new RuntimeException("Stub element type declaration for " + psiClass.getSimpleName() + " is missing required constructors",e); + } + emptyArray = (PsiT[]) Array.newInstance(psiClass, 0); + arrayFactory = count -> { + if (count == 0) { + return emptyArray; + } + return (PsiT[]) Array.newInstance(psiClass, count); + }; + } + + @NotNull + public PsiT createPsiFromAst(@NotNull ASTNode node) { + return ReflectionUtil.createInstance(byNodeConstructor, node); + } + + @Override + @NotNull + public PsiT createPsi(@NotNull StubT stub) { + return ReflectionUtil.createInstance(byStubConstructor, stub); + } + + @NotNull + @Override + public String getExternalId() { + return "kotlin." + toString(); + } + + @Override + public boolean shouldCreateStub(ASTNode node) { + PsiElement psi = node.getPsi(); + if (psi instanceof KtClassOrObject || psi instanceof KtFunction) { + return true; + } + if (psi instanceof KtProperty) { + return !((KtProperty) psi).isLocal(); + } + return createStubDependingOnParent(node); + } + + private static boolean createStubDependingOnParent(ASTNode node) { + ASTNode parent = node.getTreeParent(); + IElementType parentType = parent.getElementType(); + if (parentType instanceof IStubElementType) { + return ((IStubElementType) parentType).shouldCreateStub(parent); + } + if (parentType instanceof IStubFileElementType) { + return true; + } + return false; + } + + @Override + public void indexStub(@NotNull StubT stub, @NotNull IndexSink sink) { + // do not force inheritors to implement this method + } + + @NotNull + public ArrayFactory getArrayFactory() { + return arrayFactory; + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtValueArgumentElementType.kt.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtValueArgumentElementType.kt.203 new file mode 100644 index 00000000000..6bc4529f4b8 --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/elements/KtValueArgumentElementType.kt.203 @@ -0,0 +1,31 @@ +/* + * 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.psi.stubs.elements + +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement +import com.intellij.psi.stubs.StubInputStream +import com.intellij.psi.stubs.StubOutputStream +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.stubs.KotlinValueArgumentStub +import org.jetbrains.kotlin.psi.stubs.impl.KotlinValueArgumentStubImpl + +class KtValueArgumentElementType(debugName: String, psiClass: Class) : + KtStubElementType, T>(debugName, psiClass, KotlinValueArgumentStub::class.java) { + + override fun createStub(psi: T, parentStub: StubElement?): KotlinValueArgumentStub { + return KotlinValueArgumentStubImpl(parentStub, this, psi.isSpread) + } + + override fun serialize(stub: KotlinValueArgumentStub, dataStream: StubOutputStream) { + dataStream.writeBoolean(stub.isSpread()) + } + + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement?): KotlinValueArgumentStub { + val isSpread = dataStream.readBoolean() + return KotlinValueArgumentStubImpl(parentStub, this, isSpread) + } +} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/impl/KotlinImportAliasStubImpl.kt.203 b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/impl/KotlinImportAliasStubImpl.kt.203 new file mode 100644 index 00000000000..10c5c49bc57 --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/stubs/impl/KotlinImportAliasStubImpl.kt.203 @@ -0,0 +1,20 @@ +/* + * 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.psi.stubs.impl + +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement +import com.intellij.util.io.StringRef +import org.jetbrains.kotlin.psi.KtImportAlias +import org.jetbrains.kotlin.psi.stubs.KotlinImportAliasStub +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes + +class KotlinImportAliasStubImpl( + parent: StubElement?, + private val name: StringRef? +) : KotlinStubBaseImpl(parent, KtStubElementTypes.IMPORT_ALIAS), KotlinImportAliasStub { + override fun getName(): String? = StringRef.toString(name) +} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java.203 b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java.203 new file mode 100644 index 00000000000..09cfda93222 --- /dev/null +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/testFramework/KtUsefulTestCase.java.203 @@ -0,0 +1,1178 @@ +/* + * 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.test.testFramework; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory; +import com.intellij.diagnostic.PerformanceWatcher; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.impl.ApplicationInfoImpl; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileVisitor; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.impl.DocumentCommitProcessor; +import com.intellij.psi.impl.DocumentCommitThread; +import com.intellij.psi.impl.source.PostprocessReformattingAspect; +import com.intellij.rt.execution.junit.FileComparisonFailure; +import com.intellij.testFramework.*; +import com.intellij.testFramework.exceptionCases.AbstractExceptionCase; +import com.intellij.testFramework.fixtures.IdeaTestExecutionPolicy; +import com.intellij.util.*; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.PeekableIterator; +import com.intellij.util.containers.PeekableIteratorWrapper; +import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.FileBasedIndexImpl; +import com.intellij.util.lang.CompoundRuntimeException; +import com.intellij.util.ui.UIUtil; +import gnu.trove.Equality; +import gnu.trove.THashSet; +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; +import org.jdom.Element; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.testFramework.MockComponentManagerCreationTracer; +import org.jetbrains.kotlin.types.AbstractTypeChecker; +import org.jetbrains.kotlin.types.FlexibleTypeImpl; +import org.junit.Assert; +import org.junit.ComparisonFailure; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * @author peter + */ +@SuppressWarnings("ALL") +public abstract class KtUsefulTestCase extends TestCase { + public static final boolean IS_UNDER_TEAMCITY = System.getenv("TEAMCITY_VERSION") != null; + public static final String TEMP_DIR_MARKER = "unitTest_"; + public static final boolean OVERWRITE_TESTDATA = Boolean.getBoolean("idea.tests.overwrite.data"); + + private static final String ORIGINAL_TEMP_DIR = FileUtil.getTempDirectory(); + + private static final Map TOTAL_SETUP_COST_MILLIS = new HashMap<>(); + private static final Map TOTAL_TEARDOWN_COST_MILLIS = new HashMap<>(); + + private Application application; + + static { + IdeaForkJoinWorkerThreadFactory.setupPoisonFactory(); + Logger.setFactory(TestLoggerFactory.class); + } + protected static final Logger LOG = Logger.getInstance(KtUsefulTestCase.class); + + @NotNull + private final Disposable myTestRootDisposable = new TestDisposable(); + + static Path ourPathToKeep; + private final List myPathsToKeep = new ArrayList<>(); + + private String myTempDir; + + private static final String DEFAULT_SETTINGS_EXTERNALIZED; + private static final CodeInsightSettings defaultSettings = new CodeInsightSettings(); + static { + // Radar #5755208: Command line Java applications need a way to launch without a Dock icon. + System.setProperty("apple.awt.UIElement", "true"); + + try { + Element oldS = new Element("temp"); + defaultSettings.writeExternal(oldS); + DEFAULT_SETTINGS_EXTERNALIZED = JDOMUtil.writeElement(oldS); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + // -- KOTLIN ADDITIONAL START -- + + FlexibleTypeImpl.RUN_SLOW_ASSERTIONS = true; + AbstractTypeChecker.RUN_SLOW_ASSERTIONS = true; + + // -- KOTLIN ADDITIONAL END -- + } + + /** + * Pass here the exception you want to be thrown first + * E.g.
+     * {@code
+     *   void tearDown() {
+     *     try {
+     *       doTearDowns();
+     *     }
+     *     catch(Exception e) {
+     *       addSuppressedException(e);
+     *     }
+     *     finally {
+     *       super.tearDown();
+     *     }
+     *   }
+     * }
+     * 
+ * + */ + protected void addSuppressedException(@NotNull Throwable e) { + List list = mySuppressedExceptions; + if (list == null) { + mySuppressedExceptions = list = new SmartList<>(); + } + list.add(e); + } + private List mySuppressedExceptions; + + + public KtUsefulTestCase() { + } + + public KtUsefulTestCase(@NotNull String name) { + super(name); + } + + protected boolean shouldContainTempFiles() { + return true; + } + + @Override + protected void setUp() throws Exception { + // -- KOTLIN ADDITIONAL START -- + application = ApplicationManager.getApplication(); + + if (application != null && application.isDisposed()) { + MockComponentManagerCreationTracer.diagnoseDisposedButNotClearedApplication(application); + } + // -- KOTLIN ADDITIONAL END -- + + super.setUp(); + + if (shouldContainTempFiles()) { + IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current(); + String testName = null; + if (policy != null) { + testName = policy.getPerTestTempDirName(); + } + if (testName == null) { + testName = FileUtil.sanitizeFileName(getTestName(true)); + } + testName = new File(testName).getName(); // in case the test name contains file separators + myTempDir = FileUtil.createTempDirectory(TEMP_DIR_MARKER + testName, "", false).getPath(); + FileUtil.resetCanonicalTempPathCache(myTempDir); + } + + boolean isStressTest = isStressTest(); + ApplicationInfoImpl.setInStressTest(isStressTest); + if (isPerformanceTest()) { + Timings.getStatistics(); + } + + // turn off Disposer debugging for performance tests + Disposer.setDebugMode(!isStressTest); + + if (isIconRequired()) { + // ensure that IconLoader will use dummy empty icon + IconLoader.deactivate(); + //IconManager.activate(); + } + } + + protected boolean isIconRequired() { + return false; + } + + @Override + protected void tearDown() throws Exception { + try { + // don't use method references here to make stack trace reading easier + //noinspection Convert2MethodRef + new RunAll( + () -> { + if (isIconRequired()) { + //IconManager.deactivate(); + } + }, + () -> disposeRootDisposable(), + () -> cleanupSwingDataStructures(), + () -> cleanupDeleteOnExitHookList(), + () -> Disposer.setDebugMode(true), + () -> { + if (shouldContainTempFiles()) { + FileUtil.resetCanonicalTempPathCache(ORIGINAL_TEMP_DIR); + if (hasTmpFilesToKeep()) { + File[] files = new File(myTempDir).listFiles(); + if (files != null) { + for (File file : files) { + if (!shouldKeepTmpFile(file)) { + FileUtil.delete(file); + } + } + } + } + else { + FileUtil.delete(new File(myTempDir)); + } + } + }, + () -> waitForAppLeakingThreads(10, TimeUnit.SECONDS) + ).run(ObjectUtils.notNull(mySuppressedExceptions, Collections.emptyList())); + } + finally { + // -- KOTLIN ADDITIONAL START -- + TestApplicationUtilKt.resetApplicationToNull(application); + application = null; + // -- KOTLIN ADDITIONAL END -- + } + } + + protected final void disposeRootDisposable() { + Disposer.dispose(getTestRootDisposable()); + } + + protected void addTmpFileToKeep(@NotNull File file) { + myPathsToKeep.add(file.getPath()); + } + + private boolean hasTmpFilesToKeep() { + return ourPathToKeep != null && FileUtil.isAncestor(myTempDir, ourPathToKeep.toString(), false) || !myPathsToKeep.isEmpty(); + } + + private boolean shouldKeepTmpFile(@NotNull File file) { + String path = file.getPath(); + if (FileUtil.pathsEqual(path, ourPathToKeep.toString())) return true; + for (String pathToKeep : myPathsToKeep) { + if (FileUtil.pathsEqual(path, pathToKeep)) return true; + } + return false; + } + + private static final Set DELETE_ON_EXIT_HOOK_DOT_FILES; + private static final Class DELETE_ON_EXIT_HOOK_CLASS; + static { + Class aClass; + try { + aClass = Class.forName("java.io.DeleteOnExitHook"); + } + catch (Exception e) { + throw new RuntimeException(e); + } + @SuppressWarnings("unchecked") Set files = ReflectionUtil.getStaticFieldValue(aClass, Set.class, "files"); + DELETE_ON_EXIT_HOOK_CLASS = aClass; + DELETE_ON_EXIT_HOOK_DOT_FILES = files; + } + + @SuppressWarnings("SynchronizeOnThis") + private static void cleanupDeleteOnExitHookList() { + // try to reduce file set retained by java.io.DeleteOnExitHook + List list; + synchronized (DELETE_ON_EXIT_HOOK_CLASS) { + if (DELETE_ON_EXIT_HOOK_DOT_FILES.isEmpty()) return; + list = new ArrayList<>(DELETE_ON_EXIT_HOOK_DOT_FILES); + } + for (int i = list.size() - 1; i >= 0; i--) { + String path = list.get(i); + File file = new File(path); + if (file.delete() || !file.exists()) { + synchronized (DELETE_ON_EXIT_HOOK_CLASS) { + DELETE_ON_EXIT_HOOK_DOT_FILES.remove(path); + } + } + } + } + + @SuppressWarnings("ConstantConditions") + private static void cleanupSwingDataStructures() throws Exception { + Object manager = ReflectionUtil.getDeclaredMethod(Class.forName("javax.swing.KeyboardManager"), "getCurrentManager").invoke(null); + Map componentKeyStrokeMap = ReflectionUtil.getField(manager.getClass(), manager, Hashtable.class, "componentKeyStrokeMap"); + componentKeyStrokeMap.clear(); + Map containerMap = ReflectionUtil.getField(manager.getClass(), manager, Hashtable.class, "containerMap"); + containerMap.clear(); + } + + static void doCheckForSettingsDamage(@NotNull CodeStyleSettings oldCodeStyleSettings, @NotNull CodeStyleSettings currentCodeStyleSettings) { + final CodeInsightSettings settings = CodeInsightSettings.getInstance(); + // don't use method references here to make stack trace reading easier + //noinspection Convert2MethodRef + new RunAll() + .append(() -> { + try { + checkCodeInsightSettingsEqual(defaultSettings, settings); + } + catch (AssertionError error) { + CodeInsightSettings clean = new CodeInsightSettings(); + for (Field field : clean.getClass().getFields()) { + try { + ReflectionUtil.copyFieldValue(clean, settings, field); + } + catch (Exception ignored) { + } + } + throw error; + } + }) + .append(() -> { + currentCodeStyleSettings.getIndentOptions(StdFileTypes.JAVA); + try { + checkCodeStyleSettingsEqual(oldCodeStyleSettings, currentCodeStyleSettings); + } + finally { + currentCodeStyleSettings.clearCodeStyleSettings(); + } + }) + .run(); + } + + @NotNull + public Disposable getTestRootDisposable() { + return myTestRootDisposable; + } + + @Override + protected void runTest() throws Throwable { + final Throwable[] throwables = new Throwable[1]; + + Runnable runnable = () -> { + try { + TestLoggerFactory.onTestStarted(); + super.runTest(); + TestLoggerFactory.onTestFinished(true); + } + catch (InvocationTargetException e) { + TestLoggerFactory.onTestFinished(false); + e.fillInStackTrace(); + throwables[0] = e.getTargetException(); + } + catch (IllegalAccessException e) { + TestLoggerFactory.onTestFinished(false); + e.fillInStackTrace(); + throwables[0] = e; + } + catch (Throwable e) { + TestLoggerFactory.onTestFinished(false); + throwables[0] = e; + } + }; + + invokeTestRunnable(runnable); + + if (throwables[0] != null) { + throw throwables[0]; + } + } + + protected boolean shouldRunTest() { + return TestFrameworkUtil.canRunTest(getClass()); + } + + protected void invokeTestRunnable(@NotNull Runnable runnable) throws Exception { + if (runInDispatchThread()) { + EdtTestUtilKt.runInEdtAndWait(() -> { + runnable.run(); + return null; + }); + } + else { + runnable.run(); + } + } + + protected void defaultRunBare() throws Throwable { + Throwable exception = null; + try { + long setupStart = System.nanoTime(); + setUp(); + long setupCost = (System.nanoTime() - setupStart) / 1000000; + logPerClassCost(setupCost, TOTAL_SETUP_COST_MILLIS); + + runTest(); + } + catch (Throwable running) { + exception = running; + } + finally { + try { + long teardownStart = System.nanoTime(); + tearDown(); + long teardownCost = (System.nanoTime() - teardownStart) / 1000000; + logPerClassCost(teardownCost, TOTAL_TEARDOWN_COST_MILLIS); + } + catch (Throwable tearingDown) { + if (exception == null) { + exception = tearingDown; + } + else { + exception = new CompoundRuntimeException(Arrays.asList(exception, tearingDown)); + } + } + } + if (exception != null) { + throw exception; + } + } + + /** + * Logs the setup cost grouped by test fixture class (superclass of the current test class). + * + * @param cost setup cost in milliseconds + */ + private void logPerClassCost(long cost, @NotNull Map costMap) { + Class superclass = getClass().getSuperclass(); + Long oldCost = costMap.get(superclass.getName()); + long newCost = oldCost == null ? cost : oldCost + cost; + costMap.put(superclass.getName(), newCost); + } + + @SuppressWarnings("UseOfSystemOutOrSystemErr") + static void logSetupTeardownCosts() { + System.out.println("Setup costs"); + long totalSetup = 0; + for (Map.Entry entry : TOTAL_SETUP_COST_MILLIS.entrySet()) { + System.out.println(String.format(" %s: %d ms", entry.getKey(), entry.getValue())); + totalSetup += entry.getValue(); + } + System.out.println("Teardown costs"); + long totalTeardown = 0; + for (Map.Entry entry : TOTAL_TEARDOWN_COST_MILLIS.entrySet()) { + System.out.println(String.format(" %s: %d ms", entry.getKey(), entry.getValue())); + totalTeardown += entry.getValue(); + } + System.out.println(String.format("Total overhead: setup %d ms, teardown %d ms", totalSetup, totalTeardown)); + System.out.println(String.format("##teamcity[buildStatisticValue key='ideaTests.totalSetupMs' value='%d']", totalSetup)); + System.out.println(String.format("##teamcity[buildStatisticValue key='ideaTests.totalTeardownMs' value='%d']", totalTeardown)); + } + + @Override + public void runBare() throws Throwable { + if (!shouldRunTest()) return; + + if (runInDispatchThread()) { + TestRunnerUtil.replaceIdeEventQueueSafely(); + EdtTestUtil.runInEdtAndWait(this::defaultRunBare); + } + else { + defaultRunBare(); + } + } + + protected boolean runInDispatchThread() { + IdeaTestExecutionPolicy policy = IdeaTestExecutionPolicy.current(); + if (policy != null) { + return policy.runInDispatchThread(); + } + return true; + } + + /** + * If you want a more shorter name than runInEdtAndWait. + */ + protected void edt(@NotNull ThrowableRunnable runnable) throws Throwable { + EdtTestUtil.runInEdtAndWait(runnable); + } + + @NotNull + public static String toString(@NotNull Iterable collection) { + if (!collection.iterator().hasNext()) { + return ""; + } + + final StringBuilder builder = new StringBuilder(); + for (final Object o : collection) { + if (o instanceof THashSet) { + builder.append(new TreeSet<>((THashSet)o)); + } + else { + builder.append(o); + } + builder.append('\n'); + } + return builder.toString(); + } + + @SafeVarargs + public static void assertOrderedEquals(@NotNull T[] actual, @NotNull T... expected) { + assertOrderedEquals(Arrays.asList(actual), expected); + } + + @SafeVarargs + public static void assertOrderedEquals(@NotNull Iterable actual, @NotNull T... expected) { + assertOrderedEquals("", actual, expected); + } + + public static void assertOrderedEquals(@NotNull byte[] actual, @NotNull byte[] expected) { + assertEquals(expected.length, actual.length); + for (int i = 0; i < actual.length; i++) { + byte a = actual[i]; + byte e = expected[i]; + assertEquals("not equals at index: "+i, e, a); + } + } + + public static void assertOrderedEquals(@NotNull int[] actual, @NotNull int[] expected) { + if (actual.length != expected.length) { + fail("Expected size: "+expected.length+"; actual: "+actual.length+"\nexpected: "+Arrays.toString(expected)+"\nactual : "+Arrays.toString(actual)); + } + for (int i = 0; i < actual.length; i++) { + int a = actual[i]; + int e = expected[i]; + assertEquals("not equals at index: "+i, e, a); + } + } + + @SafeVarargs + public static void assertOrderedEquals(@NotNull String errorMsg, @NotNull Iterable actual, @NotNull T... expected) { + assertOrderedEquals(errorMsg, actual, Arrays.asList(expected)); + } + + public static void assertOrderedEquals(@NotNull Iterable actual, @NotNull Iterable expected) { + assertOrderedEquals("", actual, expected); + } + + public static void assertOrderedEquals(@NotNull String errorMsg, + @NotNull Iterable actual, + @NotNull Iterable expected) { + //noinspection unchecked + assertOrderedEquals(errorMsg, actual, expected, Equality.CANONICAL); + } + + public static void assertOrderedEquals(@NotNull String errorMsg, + @NotNull Iterable actual, + @NotNull Iterable expected, + @NotNull Equality comparator) { + if (!equals(actual, expected, comparator)) { + String expectedString = toString(expected); + String actualString = toString(actual); + Assert.assertEquals(errorMsg, expectedString, actualString); + Assert.fail("Warning! 'toString' does not reflect the difference.\nExpected: " + expectedString + "\nActual: " + actualString); + } + } + + private static boolean equals(@NotNull Iterable a1, + @NotNull Iterable a2, + @NotNull Equality comparator) { + Iterator it1 = a1.iterator(); + Iterator it2 = a2.iterator(); + while (it1.hasNext() || it2.hasNext()) { + if (!it1.hasNext() || !it2.hasNext()) return false; + if (!comparator.equals(it1.next(), it2.next())) return false; + } + return true; + } + + @SafeVarargs + public static void assertOrderedCollection(@NotNull T[] collection, @NotNull Consumer... checkers) { + assertOrderedCollection(Arrays.asList(collection), checkers); + } + + /** + * Checks {@code actual} contains same elements (in {@link #equals(Object)} meaning) as {@code expected} irrespective of their order + */ + @SafeVarargs + public static void assertSameElements(@NotNull T[] actual, @NotNull T... expected) { + assertSameElements(Arrays.asList(actual), expected); + } + + /** + * Checks {@code actual} contains same elements (in {@link #equals(Object)} meaning) as {@code expected} irrespective of their order + */ + @SafeVarargs + public static void assertSameElements(@NotNull Collection actual, @NotNull T... expected) { + assertSameElements(actual, Arrays.asList(expected)); + } + + /** + * Checks {@code actual} contains same elements (in {@link #equals(Object)} meaning) as {@code expected} irrespective of their order + */ + public static void assertSameElements(@NotNull Collection actual, @NotNull Collection expected) { + assertSameElements("", actual, expected); + } + + /** + * Checks {@code actual} contains same elements (in {@link #equals(Object)} meaning) as {@code expected} irrespective of their order + */ + public static void assertSameElements(@NotNull String message, @NotNull Collection actual, @NotNull Collection expected) { + if (actual.size() != expected.size() || !new HashSet<>(expected).equals(new HashSet(actual))) { + Assert.assertEquals(message, new HashSet<>(expected), new HashSet(actual)); + } + } + + @SafeVarargs + public static void assertContainsOrdered(@NotNull Collection collection, @NotNull T... expected) { + assertContainsOrdered(collection, Arrays.asList(expected)); + } + + public static void assertContainsOrdered(@NotNull Collection collection, @NotNull Collection expected) { + PeekableIterator expectedIt = new PeekableIteratorWrapper<>(expected.iterator()); + PeekableIterator actualIt = new PeekableIteratorWrapper<>(collection.iterator()); + + while (actualIt.hasNext() && expectedIt.hasNext()) { + T expectedElem = expectedIt.peek(); + T actualElem = actualIt.peek(); + if (expectedElem.equals(actualElem)) { + expectedIt.next(); + } + actualIt.next(); + } + if (expectedIt.hasNext()) { + throw new ComparisonFailure("", toString(expected), toString(collection)); + } + } + + @SafeVarargs + public static void assertContainsElements(@NotNull Collection collection, @NotNull T... expected) { + assertContainsElements(collection, Arrays.asList(expected)); + } + + public static void assertContainsElements(@NotNull Collection collection, @NotNull Collection expected) { + ArrayList copy = new ArrayList<>(collection); + copy.retainAll(expected); + assertSameElements(toString(collection), copy, expected); + } + + @NotNull + public static String toString(@NotNull Object[] collection, @NotNull String separator) { + return toString(Arrays.asList(collection), separator); + } + + @SafeVarargs + public static void assertDoesntContain(@NotNull Collection collection, @NotNull T... notExpected) { + assertDoesntContain(collection, Arrays.asList(notExpected)); + } + + public static void assertDoesntContain(@NotNull Collection collection, @NotNull Collection notExpected) { + ArrayList expected = new ArrayList<>(collection); + expected.removeAll(notExpected); + assertSameElements(collection, expected); + } + + @NotNull + public static String toString(@NotNull Collection collection, @NotNull String separator) { + List list = ContainerUtil.map2List(collection, String::valueOf); + Collections.sort(list); + StringBuilder builder = new StringBuilder(); + boolean flag = false; + for (final String o : list) { + if (flag) { + builder.append(separator); + } + builder.append(o); + flag = true; + } + return builder.toString(); + } + + @SafeVarargs + public static void assertOrderedCollection(@NotNull Collection collection, @NotNull Consumer... checkers) { + if (collection.size() != checkers.length) { + Assert.fail(toString(collection)); + } + int i = 0; + for (final T actual : collection) { + try { + checkers[i].consume(actual); + } + catch (AssertionFailedError e) { + //noinspection UseOfSystemOutOrSystemErr + System.out.println(i + ": " + actual); + throw e; + } + i++; + } + } + + @SafeVarargs + public static void assertUnorderedCollection(@NotNull T[] collection, @NotNull Consumer... checkers) { + assertUnorderedCollection(Arrays.asList(collection), checkers); + } + + @SafeVarargs + public static void assertUnorderedCollection(@NotNull Collection collection, @NotNull Consumer... checkers) { + if (collection.size() != checkers.length) { + Assert.fail(toString(collection)); + } + Set> checkerSet = ContainerUtil.set(checkers); + int i = 0; + Throwable lastError = null; + for (final T actual : collection) { + boolean flag = true; + for (final Consumer condition : checkerSet) { + Throwable error = accepts(condition, actual); + if (error == null) { + checkerSet.remove(condition); + flag = false; + break; + } + else { + lastError = error; + } + } + if (flag) { + //noinspection ConstantConditions,CallToPrintStackTrace + lastError.printStackTrace(); + Assert.fail("Incorrect element(" + i + "): " + actual); + } + i++; + } + } + + private static Throwable accepts(@NotNull Consumer condition, final T actual) { + try { + condition.consume(actual); + return null; + } + catch (Throwable e) { + return e; + } + } + + @Contract("null, _ -> fail") + @NotNull + public static T assertInstanceOf(Object o, @NotNull Class aClass) { + Assert.assertNotNull("Expected instance of: " + aClass.getName() + " actual: " + null, o); + Assert.assertTrue("Expected instance of: " + aClass.getName() + " actual: " + o.getClass().getName(), aClass.isInstance(o)); + @SuppressWarnings("unchecked") T t = (T)o; + return t; + } + + public static T assertOneElement(@NotNull Collection collection) { + Iterator iterator = collection.iterator(); + String toString = toString(collection); + Assert.assertTrue(toString, iterator.hasNext()); + T t = iterator.next(); + Assert.assertFalse(toString, iterator.hasNext()); + return t; + } + + public static T assertOneElement(@NotNull T[] ts) { + Assert.assertEquals(Arrays.asList(ts).toString(), 1, ts.length); + return ts[0]; + } + + @SafeVarargs + public static void assertOneOf(T value, @NotNull T... values) { + for (T v : values) { + if (Objects.equals(value, v)) { + return; + } + } + Assert.fail(value + " should be equal to one of " + Arrays.toString(values)); + } + + public static void printThreadDump() { + PerformanceWatcher.dumpThreadsToConsole("Thread dump:"); + } + + public static void assertEmpty(@NotNull Object[] array) { + assertOrderedEquals(array); + } + + public static void assertNotEmpty(final Collection collection) { + assertNotNull(collection); + assertFalse(collection.isEmpty()); + } + + public static void assertEmpty(@NotNull Collection collection) { + assertEmpty(collection.toString(), collection); + } + + public static void assertNullOrEmpty(@Nullable Collection collection) { + if (collection == null) return; + assertEmpty("", collection); + } + + public static void assertEmpty(final String s) { + assertTrue(s, StringUtil.isEmpty(s)); + } + + public static void assertEmpty(@NotNull String errorMsg, @NotNull Collection collection) { + assertOrderedEquals(errorMsg, collection, Collections.emptyList()); + } + + public static void assertSize(int expectedSize, @NotNull Object[] array) { + if (array.length != expectedSize) { + assertEquals(toString(Arrays.asList(array)), expectedSize, array.length); + } + } + + public static void assertSize(int expectedSize, @NotNull Collection c) { + if (c.size() != expectedSize) { + assertEquals(toString(c), expectedSize, c.size()); + } + } + + @NotNull + protected T disposeOnTearDown(@NotNull T disposable) { + Disposer.register(getTestRootDisposable(), disposable); + return disposable; + } + + public static void assertSameLines(@NotNull String expected, @NotNull String actual) { + assertSameLines(null, expected, actual); + } + + public static void assertSameLines(@Nullable String message, @NotNull String expected, @NotNull String actual) { + String expectedText = StringUtil.convertLineSeparators(expected.trim()); + String actualText = StringUtil.convertLineSeparators(actual.trim()); + Assert.assertEquals(message, expectedText, actualText); + } + + public static void assertExists(@NotNull File file){ + assertTrue("File should exist " + file, file.exists()); + } + + public static void assertDoesntExist(@NotNull File file){ + assertFalse("File should not exist " + file, file.exists()); + } + + @NotNull + protected String getTestName(boolean lowercaseFirstLetter) { + return getTestName(getName(), lowercaseFirstLetter); + } + + @NotNull + public static String getTestName(@Nullable String name, boolean lowercaseFirstLetter) { + return name == null ? "" : PlatformTestUtil.getTestName(name, lowercaseFirstLetter); + } + + @NotNull + protected String getTestDirectoryName() { + final String testName = getTestName(true); + return testName.replaceAll("_.*", ""); + } + + public static void assertSameLinesWithFile(@NotNull String filePath, @NotNull String actualText) { + assertSameLinesWithFile(filePath, actualText, true); + } + + public static void assertSameLinesWithFile(@NotNull String filePath, + @NotNull String actualText, + @NotNull Supplier messageProducer) { + assertSameLinesWithFile(filePath, actualText, true, messageProducer); + } + + public static void assertSameLinesWithFile(@NotNull String filePath, @NotNull String actualText, boolean trimBeforeComparing) { + assertSameLinesWithFile(filePath, actualText, trimBeforeComparing, null); + } + + public static void assertSameLinesWithFile(@NotNull String filePath, + @NotNull String actualText, + boolean trimBeforeComparing, + @Nullable Supplier messageProducer) { + String fileText; + try { + if (OVERWRITE_TESTDATA) { + VfsTestUtil.overwriteTestData(filePath, actualText); + //noinspection UseOfSystemOutOrSystemErr + System.out.println("File " + filePath + " created."); + } + fileText = FileUtil.loadFile(new File(filePath), StandardCharsets.UTF_8); + } + catch (FileNotFoundException e) { + VfsTestUtil.overwriteTestData(filePath, actualText); + throw new AssertionFailedError("No output text found. File " + filePath + " created."); + } + catch (IOException e) { + throw new RuntimeException(e); + } + String expected = StringUtil.convertLineSeparators(trimBeforeComparing ? fileText.trim() : fileText); + String actual = StringUtil.convertLineSeparators(trimBeforeComparing ? actualText.trim() : actualText); + if (!Comparing.equal(expected, actual)) { + throw new FileComparisonFailure(messageProducer == null ? null : messageProducer.get(), expected, actual, filePath); + } + } + + protected static void clearFields(@NotNull Object test) throws IllegalAccessException { + Class aClass = test.getClass(); + while (aClass != null) { + clearDeclaredFields(test, aClass); + aClass = aClass.getSuperclass(); + } + } + + public static void clearDeclaredFields(@NotNull Object test, @NotNull Class aClass) throws IllegalAccessException { + for (final Field field : aClass.getDeclaredFields()) { + final String name = field.getDeclaringClass().getName(); + if (!name.startsWith("junit.framework.") && !name.startsWith("com.intellij.testFramework.")) { + final int modifiers = field.getModifiers(); + if ((modifiers & Modifier.FINAL) == 0 && (modifiers & Modifier.STATIC) == 0 && !field.getType().isPrimitive()) { + field.setAccessible(true); + field.set(test, null); + } + } + } + } + + private static void checkCodeStyleSettingsEqual(@NotNull CodeStyleSettings expected, @NotNull CodeStyleSettings settings) { + if (!expected.equals(settings)) { + Element oldS = new Element("temp"); + expected.writeExternal(oldS); + Element newS = new Element("temp"); + settings.writeExternal(newS); + + String newString = JDOMUtil.writeElement(newS); + String oldString = JDOMUtil.writeElement(oldS); + Assert.assertEquals("Code style settings damaged", oldString, newString); + } + } + + private static void checkCodeInsightSettingsEqual(@NotNull CodeInsightSettings oldSettings, @NotNull CodeInsightSettings settings) { + if (!oldSettings.equals(settings)) { + Element newS = new Element("temp"); + settings.writeExternal(newS); + Assert.assertEquals("Code insight settings damaged", DEFAULT_SETTINGS_EXTERNALIZED, JDOMUtil.writeElement(newS)); + } + } + + public boolean isPerformanceTest() { + String testName = getName(); + String className = getClass().getSimpleName(); + return TestFrameworkUtil.isPerformanceTest(testName, className); + } + + /** + * @return true for a test which performs A LOT of computations. + * Such test should typically avoid performing expensive checks, e.g. data structure consistency complex validations. + * If you want your test to be treated as "Stress", please mention one of these words in its name: "Stress", "Slow". + * For example: {@code public void testStressPSIFromDifferentThreads()} + */ + public boolean isStressTest() { + return isStressTest(getName(), getClass().getName()); + } + + private static boolean isStressTest(String testName, String className) { + return TestFrameworkUtil.isPerformanceTest(testName, className) || + containsStressWords(testName) || + containsStressWords(className); + } + + private static boolean containsStressWords(@Nullable String name) { + return name != null && (name.contains("Stress") || name.contains("Slow")); + } + + public static void doPostponedFormatting(@NotNull Project project) { + DocumentUtil.writeInRunUndoTransparentAction(() -> { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + PostprocessReformattingAspect.getInstance(project).doPostponedFormatting(); + }); + } + + /** + * Checks that code block throw corresponding exception. + * + * @param exceptionCase Block annotated with some exception type + */ + protected void assertException(@NotNull AbstractExceptionCase exceptionCase) { + assertException(exceptionCase, null); + } + + /** + * Checks that code block throw corresponding exception with expected error msg. + * If expected error message is null it will not be checked. + * + * @param exceptionCase Block annotated with some exception type + * @param expectedErrorMsg expected error message + */ + protected void assertException(@NotNull AbstractExceptionCase exceptionCase, @Nullable String expectedErrorMsg) { + //noinspection unchecked + assertExceptionOccurred(true, exceptionCase, expectedErrorMsg); + } + + /** + * Checks that the code block throws an exception of the specified class. + * + * @param exceptionClass Expected exception type + * @param runnable Block annotated with some exception type + */ + public static void assertThrows(@NotNull Class exceptionClass, + @NotNull ThrowableRunnable runnable) { + assertThrows(exceptionClass, null, runnable); + } + + /** + * Checks that the code block throws an exception of the specified class with expected error msg. + * If expected error message is null it will not be checked. + * + * @param exceptionClass Expected exception type + * @param expectedErrorMsgPart expected error message, of any + * @param runnable Block annotated with some exception type + */ + @SuppressWarnings({"unchecked", "SameParameterValue"}) + public static void assertThrows(@NotNull Class exceptionClass, + @Nullable String expectedErrorMsgPart, + @NotNull ThrowableRunnable runnable) { + assertExceptionOccurred(true, new AbstractExceptionCase() { + @Override + public Class getExpectedExceptionClass() { + return (Class)exceptionClass; + } + + @Override + public void tryClosure() throws Throwable { + runnable.run(); + } + }, expectedErrorMsgPart); + } + + /** + * Checks that code block doesn't throw corresponding exception. + * + * @param exceptionCase Block annotated with some exception type + */ + protected void assertNoException(@NotNull AbstractExceptionCase exceptionCase) throws T { + assertExceptionOccurred(false, exceptionCase, null); + } + + protected void assertNoThrowable(@NotNull Runnable closure) { + String throwableName = null; + try { + closure.run(); + } + catch (Throwable thr) { + throwableName = thr.getClass().getName(); + } + assertNull(throwableName); + } + + private static void assertExceptionOccurred(boolean shouldOccur, + @NotNull AbstractExceptionCase exceptionCase, + String expectedErrorMsgPart) throws T { + boolean wasThrown = false; + try { + exceptionCase.tryClosure(); + } + catch (Throwable e) { + Throwable cause = e; + + if (shouldOccur) { + wasThrown = true; + assertInstanceOf(cause, exceptionCase.getExpectedExceptionClass()); + if (expectedErrorMsgPart != null) { + assertTrue(cause.getMessage(), cause.getMessage().contains(expectedErrorMsgPart)); + } + } + else if (exceptionCase.getExpectedExceptionClass().equals(cause.getClass())) { + wasThrown = true; + + //noinspection UseOfSystemOutOrSystemErr + System.out.println(); + //noinspection UseOfSystemOutOrSystemErr + e.printStackTrace(System.out); + + fail("Exception isn't expected here. Exception message: " + cause.getMessage()); + } + else { + throw e; + } + } + finally { + if (shouldOccur && !wasThrown) { + fail(exceptionCase.getExpectedExceptionClass().getName() + " must be thrown."); + } + } + } + + protected boolean annotatedWith(@NotNull Class annotationClass) { + Class aClass = getClass(); + String methodName = "test" + getTestName(false); + boolean methodChecked = false; + while (aClass != null && aClass != Object.class) { + if (aClass.getAnnotation(annotationClass) != null) return true; + if (!methodChecked) { + Method method = ReflectionUtil.getDeclaredMethod(aClass, methodName); + if (method != null) { + if (method.getAnnotation(annotationClass) != null) return true; + methodChecked = true; + } + } + aClass = aClass.getSuperclass(); + } + return false; + } + + @NotNull + protected String getHomePath() { + return PathManager.getHomePath().replace(File.separatorChar, '/'); + } + + public static void refreshRecursively(@NotNull VirtualFile file) { + VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() { + @Override + public boolean visitFile(@NotNull VirtualFile file) { + file.getChildren(); + return true; + } + }); + file.refresh(false, true); + } + + public static VirtualFile refreshAndFindFile(@NotNull final File file) { + return UIUtil.invokeAndWaitIfNeeded(() -> LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)); + } + + public static void waitForAppLeakingThreads(long timeout, @NotNull TimeUnit timeUnit) throws Exception { + EdtTestUtil.runInEdtAndWait(() -> { + Application app = ApplicationManager.getApplication(); + if (app != null && !app.isDisposed()) { + FileBasedIndexImpl index = (FileBasedIndexImpl)app.getServiceIfCreated(FileBasedIndex.class); + if (index != null) { + index.getChangedFilesCollector().waitForVfsEventsExecuted(timeout, timeUnit); + } + + DocumentCommitThread commitThread = (DocumentCommitThread)app.getServiceIfCreated(DocumentCommitProcessor.class); + if (commitThread != null) { + commitThread.waitForAllCommits(timeout, timeUnit); + } + } + }); + } + + protected class TestDisposable implements Disposable { + private volatile boolean myDisposed; + + public TestDisposable() { + } + + @Override + public void dispose() { + myDisposed = true; + } + + public boolean isDisposed() { + return myDisposed; + } + + @Override + public String toString() { + String testName = getTestName(false); + return KtUsefulTestCase.this.getClass() + (StringUtil.isEmpty(testName) ? "" : ".test" + testName); + } + } +} \ No newline at end of file diff --git a/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KotlinCompletionTestCase.java.203 b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KotlinCompletionTestCase.java.203 new file mode 100644 index 00000000000..98e99e4c987 --- /dev/null +++ b/idea/idea-completion/tests/org/jetbrains/kotlin/idea/completion/test/KotlinCompletionTestCase.java.203 @@ -0,0 +1,37 @@ +/* + * 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.completion.test; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.completion.CompletionTestCase; +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ThrowableRunnable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.WithMutedInDatabaseRunTest; + +@WithMutedInDatabaseRunTest +abstract public class KotlinCompletionTestCase extends CompletionTestCase { + @Override + protected void setUp() throws Exception { + super.setUp(); + VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory()); + CodeInsightSettings.getInstance().EXCLUDED_PACKAGES = new String[]{"excludedPackage", "somePackage.ExcludedClass"}; + } + + @Override + protected void tearDown() throws Exception { + CodeInsightSettings.getInstance().EXCLUDED_PACKAGES = ArrayUtil.EMPTY_STRING_ARRAY; + VfsRootAccess.disallowRootAccess(KotlinTestUtils.getHomeDirectory()); + super.tearDown(); + } + + @Override + protected void runTestRunnable(@NotNull ThrowableRunnable testRunnable) throws Throwable { + KotlinTestUtils.runTestWithThrowable(this, () -> super.runTestRunnable(testRunnable)); + } +} diff --git a/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinCodeInsightTestCase.java.203 b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinCodeInsightTestCase.java.203 new file mode 100644 index 00000000000..652c418a3fc --- /dev/null +++ b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinCodeInsightTestCase.java.203 @@ -0,0 +1,37 @@ +/* + * 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.test; + +import com.intellij.codeInsight.CodeInsightTestCase; +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; +import com.intellij.util.ThrowableRunnable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.WithMutedInDatabaseRunTest; + +/** + * Please use KotlinLightCodeInsightFixtureTestCase as the base class for all new tests. + */ +@WithMutedInDatabaseRunTest +@Deprecated +public abstract class KotlinCodeInsightTestCase extends CodeInsightTestCase { + @Override + protected void setUp() throws Exception { + VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory()); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + VfsRootAccess.disallowRootAccess(KotlinTestUtils.getHomeDirectory()); + } + + @Override + protected void runTestRunnable(@NotNull ThrowableRunnable testRunnable) throws Throwable { + KotlinTestUtils.runTestWithThrowable(this, () -> super.runTestRunnable(testRunnable)); + } +} diff --git a/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCaseBase.java.203 b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCaseBase.java.203 new file mode 100644 index 00000000000..79bf80cf139 --- /dev/null +++ b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCaseBase.java.203 @@ -0,0 +1,99 @@ +/* + * 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.test; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.testFramework.TempFiles; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import com.intellij.util.ThrowableRunnable; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.WithMutedInDatabaseRunTest; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Collection; + +@WithMutedInDatabaseRunTest +public abstract class KotlinLightCodeInsightFixtureTestCaseBase extends LightCodeInsightFixtureTestCase { + @NotNull + @Override + public Project getProject() { + return super.getProject(); + } + + @NotNull + @Override + public Editor getEditor() { + return super.getEditor(); + } + + @Override + public PsiFile getFile() { + return super.getFile(); + } + + protected final Collection myFilesToDelete = new THashSet<>(); + private final TempFiles myTempFiles = new TempFiles(myFilesToDelete); + + @Override + protected void tearDown() throws Exception { + myTempFiles.deleteAll(); + super.tearDown(); + } + + @NotNull + public VirtualFile createTempFile( + @NonNls @NotNull String ext, + @Nullable byte[] bom, + @NonNls @NotNull String content, + @NotNull Charset charset + ) throws IOException { + File temp = FileUtil.createTempFile("copy", "." + ext); + setContentOnDisk(temp, bom, content, charset); + + myFilesToDelete.add(temp.toPath()); + final VirtualFile file = getVirtualFile(temp); + assert file != null : temp; + return file; + } + + public static void setContentOnDisk(@NotNull File file, @Nullable byte[] bom, @NotNull String content, @NotNull Charset charset) + throws IOException { + FileOutputStream stream = new FileOutputStream(file); + if (bom != null) { + stream.write(bom); + } + try (OutputStreamWriter writer = new OutputStreamWriter(stream, charset)) { + writer.write(content); + } + } + + protected static VirtualFile getVirtualFile(@NotNull File file) { + return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + } + + @Override + protected void runTestRunnable(@NotNull ThrowableRunnable testRunnable) throws Throwable { + KotlinTestUtils.runTestWithThrowable(this, () -> super.runTestRunnable(testRunnable)); + } + + protected boolean isFirPlugin() { + return false; + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/NewKotlinFileAction.kt.203 b/idea/src/org/jetbrains/kotlin/idea/actions/NewKotlinFileAction.kt.203 new file mode 100644 index 00000000000..df7ab319f36 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/NewKotlinFileAction.kt.203 @@ -0,0 +1,263 @@ +/* + * 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.actions + +import com.intellij.ide.actions.CreateFileFromTemplateAction +import com.intellij.ide.actions.CreateFileFromTemplateDialog +import com.intellij.ide.actions.CreateFromTemplateAction +import com.intellij.ide.fileTemplates.FileTemplate +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.ide.fileTemplates.actions.AttributesDefaults +import com.intellij.ide.fileTemplates.ui.CreateFromTemplateDialog +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.actionSystem.LangDataKeys +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.editor.LogicalPosition +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.ui.InputValidatorEx +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFile +import com.intellij.util.IncorrectOperationException +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.KotlinIcons +import org.jetbrains.kotlin.idea.statistics.FUSEventGroups +import org.jetbrains.kotlin.idea.statistics.KotlinFUSLogger +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.parsing.KotlinParserDefinition.Companion.STD_SCRIPT_SUFFIX +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import java.util.* + +class NewKotlinFileAction : CreateFileFromTemplateAction( + KotlinBundle.message("action.new.file.text"), + KotlinBundle.message("action.new.file.description"), + KotlinFileType.INSTANCE.icon +), DumbAware { + override fun postProcess(createdElement: PsiFile, templateName: String?, customProperties: Map?) { + super.postProcess(createdElement, templateName, customProperties) + + val module = ModuleUtilCore.findModuleForPsiElement(createdElement!!) + + if (createdElement is KtFile) { + if (module != null) { + for (hook in NewKotlinFileHook.EP_NAME.extensions) { + hook.postProcess(createdElement, module) + } + } + + val ktClass = createdElement.declarations.singleOrNull() as? KtNamedDeclaration + if (ktClass != null) { + CreateFromTemplateAction.moveCaretAfterNameIdentifier(ktClass) + } else { + val editor = FileEditorManager.getInstance(createdElement.project).selectedTextEditor ?: return + if (editor.document == createdElement.viewProvider.document) { + val lineCount = editor.document.lineCount + if (lineCount > 0) { + editor.caretModel.moveToLogicalPosition(LogicalPosition(lineCount - 1, 0)) + } + } + } + } + } + + override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { + builder.setTitle(KotlinBundle.message("action.new.file.dialog.title")) + .addKind( + KotlinBundle.message("action.new.file.dialog.class.title"), + KotlinIcons.CLASS, + "Kotlin Class" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.file.title"), + KotlinFileType.INSTANCE.icon, + "Kotlin File" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.interface.title"), + KotlinIcons.INTERFACE, + "Kotlin Interface" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.data.class.title"), + KotlinIcons.CLASS, + "Kotlin Data Class" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.enum.title"), + KotlinIcons.ENUM, + "Kotlin Enum" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.sealed.class.title"), + KotlinIcons.CLASS, + "Kotlin Sealed Class" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.annotation.title"), + KotlinIcons.ANNOTATION, + "Kotlin Annotation" + ) + .addKind( + KotlinBundle.message("action.new.file.dialog.object.title"), + KotlinIcons.OBJECT, + "Kotlin Object" + ) + + builder.setValidator(NameValidator) + } + + override fun getActionName(directory: PsiDirectory, newName: String, templateName: String): String = + KotlinBundle.message("action.new.file.text") + + override fun isAvailable(dataContext: DataContext): Boolean { + if (super.isAvailable(dataContext)) { + val ideView = LangDataKeys.IDE_VIEW.getData(dataContext)!! + val project = PlatformDataKeys.PROJECT.getData(dataContext)!! + val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex + return ideView.directories.any { projectFileIndex.isInSourceContent(it.virtualFile) } + } + + return false + } + + override fun hashCode(): Int = 0 + + override fun equals(other: Any?): Boolean = other is NewKotlinFileAction + + override fun startInWriteAction() = false + + override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory) = + createFileFromTemplateWithStat(name, template, dir) + + companion object { + private object NameValidator : InputValidatorEx { + override fun getErrorText(inputString: String): String? { + if (inputString.trim().isEmpty()) { + return KotlinBundle.message("action.new.file.error.empty.name") + } + + val parts: List = inputString.split(*FQNAME_SEPARATORS) + if (parts.any { it.trim().isEmpty() }) { + return KotlinBundle.message("action.new.file.error.empty.name.part") + } + + return null + } + + override fun checkInput(inputString: String): Boolean = true + + override fun canClose(inputString: String): Boolean = getErrorText(inputString) == null + } + + @get:TestOnly + val nameValidator: InputValidatorEx + get() = NameValidator + + private fun findOrCreateTarget(dir: PsiDirectory, name: String, directorySeparators: CharArray): Pair { + var className = removeKotlinExtensionIfPresent(name) + var targetDir = dir + + for (splitChar in directorySeparators) { + if (splitChar in className) { + val names = className.trim().split(splitChar) + + for (dirName in names.dropLast(1)) { + targetDir = targetDir.findSubdirectory(dirName) ?: runWriteAction { + targetDir.createSubdirectory(dirName) + } + } + + className = names.last() + break + } + } + return Pair(className, targetDir) + } + + private fun removeKotlinExtensionIfPresent(name: String): String = when { + name.endsWith(".$KOTLIN_WORKSHEET_EXTENSION") -> name.removeSuffix(".$KOTLIN_WORKSHEET_EXTENSION") + name.endsWith(".$STD_SCRIPT_SUFFIX") -> name.removeSuffix(".$STD_SCRIPT_SUFFIX") + name.endsWith(".${KotlinFileType.EXTENSION}") -> name.removeSuffix(".${KotlinFileType.EXTENSION}") + else -> name + } + + private fun createFromTemplate(dir: PsiDirectory, className: String, template: FileTemplate): PsiFile? { + val project = dir.project + val defaultProperties = FileTemplateManager.getInstance(project).defaultProperties + + val properties = Properties(defaultProperties) + + val element = try { + CreateFromTemplateDialog( + project, dir, template, + AttributesDefaults(className).withFixedName(true), + properties + ).create() + } catch (e: IncorrectOperationException) { + throw e + } catch (e: Exception) { + LOG.error(e) + return null + } + + return element?.containingFile + } + + private val FILE_SEPARATORS = charArrayOf('/', '\\') + private val FQNAME_SEPARATORS = charArrayOf('/', '\\', '.') + + fun createFileFromTemplateWithStat(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { + KotlinFUSLogger.log(FUSEventGroups.NewFileTemplate, template.name) + return createFileFromTemplate(name, template, dir) + } + + + fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { + val directorySeparators = when (template.name) { + "Kotlin File" -> FILE_SEPARATORS + else -> FQNAME_SEPARATORS + } + val (className, targetDir) = findOrCreateTarget(dir, name, directorySeparators) + + val service = DumbService.getInstance(dir.project) + service.isAlternativeResolveEnabled = true + try { + val psiFile = createFromTemplate(targetDir, className, template) + if (psiFile is KtFile) { + val singleClass = psiFile.declarations.singleOrNull() as? KtClass + if (singleClass != null && !singleClass.isEnum() && !singleClass.isInterface() && name.contains("Abstract")) { + runWriteAction { + singleClass.addModifier(KtTokens.ABSTRACT_KEYWORD) + } + } + } + return psiFile + } finally { + service.isAlternativeResolveEnabled = false + } + } + } +} + +abstract class NewKotlinFileHook { + companion object { + val EP_NAME: ExtensionPointName = + ExtensionPointName.create("org.jetbrains.kotlin.newFileHook") + } + + abstract fun postProcess(createdElement: KtFile, module: Module) +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinInTempDirTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinInTempDirTest.kt.203 new file mode 100644 index 00000000000..cfcd81c9c2b --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinInTempDirTest.kt.203 @@ -0,0 +1,38 @@ +/* + * 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.configuration + +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime +import org.junit.Assert +import java.io.File +import java.nio.file.Path + +abstract class AbstractConfigureKotlinInTempDirTest : AbstractConfigureKotlinTest() { + override fun getProjectDirOrFile(): Path { + val tempDir = FileUtil.generateRandomTemporaryPath() + FileUtil.createTempDirectory("temp", null) + myFilesToDelete.add(tempDir.toPath()) + + FileUtil.copyDir(File(projectRoot), tempDir) + + val kotlinRuntime = File(tempDir, "lib/kotlin-stdlib.jar") + if (getTestName(true).toLowerCase().contains("latestruntime") && kotlinRuntime.exists()) { + ForTestCompileRuntime.runtimeJarForTests().copyTo(kotlinRuntime, overwrite = true) + } + + val projectRoot = tempDir.path + + val projectFilePath = projectRoot + "/projectFile.ipr" + if (!File(projectFilePath).exists()) { + val dotIdeaPath = projectRoot + "/.idea" + Assert.assertTrue("Project file or '.idea' dir should exists in " + projectRoot, File(dotIdeaPath).exists()) + return File(projectRoot).toPath() + } + + return File(projectFilePath).toPath() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinTest.kt.203 new file mode 100644 index 00000000000..d454e5dc70e --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/AbstractConfigureKotlinTest.kt.203 @@ -0,0 +1,256 @@ +/* + * 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.configuration + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathMacros +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess +import com.intellij.testFramework.PlatformTestCase +import com.intellij.testFramework.UsefulTestCase +import com.intellij.util.ThrowableRunnable +import junit.framework.TestCase +import org.jetbrains.kotlin.idea.configuration.KotlinWithLibraryConfigurator.FileState +import org.jetbrains.kotlin.idea.framework.KotlinSdkType +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase.* +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.WithMutedInDatabaseRunTest +import org.jetbrains.kotlin.test.runTest +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +@WithMutedInDatabaseRunTest +abstract class AbstractConfigureKotlinTest : PlatformTestCase() { + override fun setUp() { + super.setUp() + + VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory()) + } + + @Throws(Exception::class) + override fun tearDown() { + VfsRootAccess.disallowRootAccess(KotlinTestUtils.getHomeDirectory()) + PathMacros.getInstance().removeMacro(TEMP_DIR_MACRO_KEY) + + super.tearDown() + } + + @Throws(Exception::class) + override fun initApplication() { + super.initApplication() + + KotlinSdkType.setUpIfNeeded(testRootDisposable) + + ApplicationManager.getApplication().runWriteAction { + addJdk(testRootDisposable, ::mockJdk6) + addJdk(testRootDisposable, ::mockJdk8) + addJdk(testRootDisposable, ::mockJdk9) + } + + val tempLibDir = FileUtil.createTempDirectory("temp", null) + PathMacros.getInstance().setMacro(TEMP_DIR_MACRO_KEY, FileUtilRt.toSystemDependentName(tempLibDir.absolutePath)) + } + + protected fun doTestConfigureModulesWithNonDefaultSetup(configurator: KotlinWithLibraryConfigurator) { + assertNoFilesInDefaultPaths() + + val modules = modules + for (module in modules) { + assertNotConfigured(module, configurator) + } + + configurator.configure(myProject, emptyList()) + + assertNoFilesInDefaultPaths() + + for (module in modules) { + assertProperlyConfigured(module, configurator) + } + } + + protected fun doTestOneJavaModule(jarState: FileState) { + doTestOneModule(jarState, JAVA_CONFIGURATOR) + } + + protected fun doTestOneJsModule(jarState: FileState) { + doTestOneModule(jarState, JS_CONFIGURATOR) + } + + private fun doTestOneModule(jarState: FileState, configurator: KotlinWithLibraryConfigurator) { + val module = module + + assertNotConfigured(module, configurator) + configure(module, jarState, configurator) + assertProperlyConfigured(module, configurator) + } + + override fun getModule(): Module { + val modules = ModuleManager.getInstance(myProject).modules + assert(modules.size == 1) { "One module should be loaded " + modules.size } + myModule = modules[0] + return super.getModule() + } + + val modules: Array + get() = ModuleManager.getInstance(myProject).modules + + override fun getProjectDirOrFile(): Path { + val projectFilePath = projectRoot + "/projectFile.ipr" + TestCase.assertTrue("Project file should exists " + projectFilePath, File(projectFilePath).exists()) + return File(projectFilePath).toPath() + } + + override fun doCreateAndOpenProject(projectFile: Path): Project { + return loadProjectCompat(projectFile) + } + + private val projectName: String + get() { + val testName = getTestName(true) + return if (testName.contains("_")) { + testName.substring(0, testName.indexOf("_")) + } else + testName + } + + protected val projectRoot: String + get() = BASE_PATH + projectName + + override fun setUpModule() {} + + private fun assertNoFilesInDefaultPaths() { + UsefulTestCase.assertDoesntExist(File(JAVA_CONFIGURATOR.getDefaultPathToJarFile(project))) + UsefulTestCase.assertDoesntExist(File(JS_CONFIGURATOR.getDefaultPathToJarFile(project))) + } + + override fun runTestRunnable(testRunnable: ThrowableRunnable) { + return runTest { super.runTestRunnable(testRunnable) } + } + + companion object { + private val BASE_PATH = "idea/testData/configuration/" + private val TEMP_DIR_MACRO_KEY = "TEMP_TEST_DIR" + protected val JAVA_CONFIGURATOR: KotlinJavaModuleConfigurator by lazy { + object : KotlinJavaModuleConfigurator() { + override fun getDefaultPathToJarFile(project: Project) = getPathRelativeToTemp("default_jvm_lib") + } + } + + protected val JS_CONFIGURATOR: KotlinJsModuleConfigurator by lazy { + object : KotlinJsModuleConfigurator() { + override fun getDefaultPathToJarFile(project: Project) = getPathRelativeToTemp("default_js_lib") + } + } + + private fun configure( + modules: List, + runtimeState: FileState, + configurator: KotlinWithLibraryConfigurator, + jarFromDist: String, + jarFromTemp: String + ) { + val project = modules.first().project + val collector = createConfigureKotlinNotificationCollector(project) + + val pathToJar = getPathToJar(runtimeState, jarFromDist, jarFromTemp) + for (module in modules) { + configurator.configureModule(module, pathToJar, pathToJar, collector, runtimeState) + } + collector.showNotification() + } + + private fun getPathToJar(runtimeState: FileState, jarFromDist: String, jarFromTemp: String) = when (runtimeState) { + FileState.EXISTS -> jarFromDist + FileState.COPY -> jarFromTemp + FileState.DO_NOT_COPY -> jarFromDist + } + + + private val pathToExistentRuntimeJar: String + get() = PathUtil.kotlinPathsForDistDirectory.stdlibPath.parent + + private val pathToExistentJsJar: String + get() = PathUtil.kotlinPathsForDistDirectory.jsStdLibJarPath.parent + + protected fun assertNotConfigured(module: Module, configurator: KotlinWithLibraryConfigurator) { + TestCase.assertFalse( + String.format("Module %s should not be configured as %s Module", module.name, configurator.presentableText), + configurator.isConfigured(module) + ) + } + + protected fun assertConfigured(module: Module, configurator: KotlinWithLibraryConfigurator) { + TestCase.assertTrue( + String.format( + "Module %s should be configured with configurator '%s'", module.name, + configurator.presentableText + ), + configurator.isConfigured(module) + ) + } + + protected fun assertProperlyConfigured(module: Module, configurator: KotlinWithLibraryConfigurator) { + assertConfigured(module, configurator) + assertNotConfigured(module, getOppositeConfigurator(configurator)) + } + + private fun getOppositeConfigurator(configurator: KotlinWithLibraryConfigurator): KotlinWithLibraryConfigurator { + if (configurator === JAVA_CONFIGURATOR) return JS_CONFIGURATOR + if (configurator === JS_CONFIGURATOR) return JAVA_CONFIGURATOR + + throw IllegalArgumentException("Only JS_CONFIGURATOR and JAVA_CONFIGURATOR are supported") + } + + private fun getPathRelativeToTemp(relativePath: String): String { + val tempPath = PathMacros.getInstance().getValue(TEMP_DIR_MACRO_KEY) + return tempPath + '/' + relativePath + } + } + + protected fun configure(module: Module, jarState: FileState, configurator: KotlinProjectConfigurator) { + if (configurator is KotlinJavaModuleConfigurator) { + configure( + listOf(module), jarState, + configurator as KotlinWithLibraryConfigurator, + pathToExistentRuntimeJar, pathToNonexistentRuntimeJar + ) + } + if (configurator is KotlinJsModuleConfigurator) { + configure( + listOf(module), jarState, + configurator as KotlinWithLibraryConfigurator, + pathToExistentJsJar, pathToNonexistentJsJar + ) + } + } + + private val pathToNonexistentRuntimeJar: String + get() { + val pathToTempKotlinRuntimeJar = FileUtil.getTempDirectory() + "/" + PathUtil.KOTLIN_JAVA_STDLIB_JAR + myFilesToDelete.add(Paths.get(pathToTempKotlinRuntimeJar)) + return pathToTempKotlinRuntimeJar + } + + private val pathToNonexistentJsJar: String + get() { + val pathToTempKotlinRuntimeJar = FileUtil.getTempDirectory() + "/" + PathUtil.JS_LIB_JAR_NAME + myFilesToDelete.add(Paths.get(pathToTempKotlinRuntimeJar)) + return pathToTempKotlinRuntimeJar + } + + override fun getTestProjectJdk(): Sdk { + val projectRootManager = ProjectRootManager.getInstance(project) + return projectRootManager.projectSdk ?: throw IllegalStateException("SDK ${projectRootManager.projectSdkName} was not found") + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/editor/quickDoc/QuickDocInHierarchyTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/editor/quickDoc/QuickDocInHierarchyTest.kt.203 new file mode 100644 index 00000000000..e4dc40fc64a --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/editor/quickDoc/QuickDocInHierarchyTest.kt.203 @@ -0,0 +1,50 @@ +/* + * 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.editor.quickDoc + +import com.intellij.codeInsight.CodeInsightTestCase +import com.intellij.ide.hierarchy.HierarchyBrowserBaseEx +import com.intellij.ide.hierarchy.LanguageTypeHierarchy +import com.intellij.ide.hierarchy.actions.BrowseHierarchyActionBase +import com.intellij.ide.hierarchy.type.TypeHierarchyNodeDescriptor +import com.intellij.ide.hierarchy.type.TypeHierarchyTreeStructure +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.testFramework.MapDataContext +import junit.framework.TestCase +import org.jetbrains.kotlin.idea.KotlinDocumentationProvider +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner +import org.junit.runner.RunWith + +@RunWith(JUnit3WithIdeaConfigurationRunner::class) +class QuickDocInHierarchyTest() : CodeInsightTestCase() { + override fun getTestDataPath(): String { + return PluginTestCaseBase.getTestDataPathBase() + "/kdoc/inTypeHierarchy/" + } + + fun testSimple() { + configureByFile(getTestName(true) + ".kt") + + val context = MapDataContext() + context.put(CommonDataKeys.PROJECT, project) + context.put(CommonDataKeys.EDITOR, editor) + + val provider = BrowseHierarchyActionBase.findProvider(LanguageTypeHierarchy.INSTANCE, file, file, context)!! + val hierarchyTreeStructure = TypeHierarchyTreeStructure( + project, + provider.getTarget(context) as PsiClass, + HierarchyBrowserBaseEx.SCOPE_PROJECT + ) + val hierarchyNodeDescriptor = hierarchyTreeStructure.baseDescriptor as TypeHierarchyNodeDescriptor + val doc = KotlinDocumentationProvider().generateDoc(hierarchyNodeDescriptor.psiClass as PsiElement, null)!! + + TestCase.assertTrue("Invalid doc\n: $doc", doc.contains("Very special class")) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt.203 new file mode 100644 index 00000000000..fe05f292a90 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/perf/UltraLightChecker.kt.203 @@ -0,0 +1,336 @@ +/* + * 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.perf + +import com.intellij.openapi.Disposable +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.psi.* +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.MethodSignature +import com.intellij.testFramework.UsefulTestCase +import com.intellij.util.PairProcessor +import com.intellij.util.ref.DebugReflectionUtil +import junit.framework.TestCase +import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport +import org.jetbrains.kotlin.asJava.LightClassGenerationSupport +import org.jetbrains.kotlin.asJava.classes.* +import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation +import org.jetbrains.kotlin.asJava.elements.KtLightPsiArrayInitializerMemberValue +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.project.languageVersionSettings +import org.jetbrains.kotlin.load.kotlin.NON_EXISTENT_CLASS_NAME +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtScript +import org.junit.Assert +import kotlin.test.assertFails + +fun UsefulTestCase.forceUsingOldLightClassesForTest() { + KtUltraLightSupport.forceUsingOldLightClasses = true + Disposer.register(testRootDisposable, Disposable { + KtUltraLightSupport.forceUsingOldLightClasses = false + }) +} + +object UltraLightChecker { + fun checkClassEquivalence(file: KtFile) { + for (ktClass in allClasses(file)) { + checkClassEquivalence(ktClass) + } + } + + fun checkForReleaseCoroutine(sourceFileText: String, module: Module) { + if (sourceFileText.contains("//RELEASE_COROUTINE_NEEDED")) { + TestCase.assertTrue( + "Test should be runned under language version that supports released coroutines", + module.languageVersionSettings.supportsFeature(LanguageFeature.ReleaseCoroutines) + ) + } + } + + fun allClasses(file: KtFile): List = + SyntaxTraverser.psiTraverser(file).filter(KtClassOrObject::class.java).toList() + + fun checkFacadeEquivalence( + fqName: FqName, + searchScope: GlobalSearchScope, + project: Project + ): KtLightClassForFacade? { + + val oldForceFlag = KtUltraLightSupport.forceUsingOldLightClasses + KtUltraLightSupport.forceUsingOldLightClasses = true + val gold = KtLightClassForFacade.createForFacadeNoCache(fqName, searchScope, project) + KtUltraLightSupport.forceUsingOldLightClasses = false + val ultraLightClass = KtLightClassForFacade.createForFacadeNoCache(fqName, searchScope, project) ?: return null + KtUltraLightSupport.forceUsingOldLightClasses = oldForceFlag + + checkClassEquivalenceByRendering(gold, ultraLightClass) + + return ultraLightClass + } + + fun checkClassEquivalence(ktClass: KtClassOrObject): KtUltraLightClass? { + val gold = KtLightClassForSourceDeclaration.createNoCache(ktClass, forceUsingOldLightClasses = true) + val ultraLightClass = LightClassGenerationSupport.getInstance(ktClass.project).createUltraLightClass(ktClass) ?: return null + + val secondULInstance = LightClassGenerationSupport.getInstance(ktClass.project).createUltraLightClass(ktClass) + Assert.assertNotNull(secondULInstance) + Assert.assertTrue(ultraLightClass !== secondULInstance) + secondULInstance!! + Assert.assertEquals(ultraLightClass.ownMethods.size, secondULInstance.ownMethods.size) + Assert.assertTrue(ultraLightClass.ownMethods.containsAll(secondULInstance.ownMethods)) + + checkClassEquivalenceByRendering(gold, ultraLightClass) + + return ultraLightClass + } + + fun checkScriptEquivalence(script: KtScript): KtLightClass { + + val ultraLightScript: KtLightClass? + + val oldForceFlag = KtUltraLightSupport.forceUsingOldLightClasses + try { + KtUltraLightSupport.forceUsingOldLightClasses = false + ultraLightScript = KotlinAsJavaSupport.getInstance(script.project).getLightClassForScript(script) + TestCase.assertTrue(ultraLightScript is KtUltraLightClassForScript) + ultraLightScript!! + val gold = KtLightClassForScript.createNoCache(script, forceUsingOldLightClasses = true) + checkClassEquivalenceByRendering(gold, ultraLightScript) + } finally { + KtUltraLightSupport.forceUsingOldLightClasses = oldForceFlag + } + + return ultraLightScript!! + } + + private fun checkClassEquivalenceByRendering(gold: PsiClass?, ultraLightClass: PsiClass) { + if (gold != null) { + Assert.assertFalse(gold.javaClass.name.contains("Ultra")) + } + + val goldText = gold?.renderClass().orEmpty() + val ultraText = ultraLightClass.renderClass() + + if (goldText != ultraText) { + Assert.assertEquals( + "// Classic implementation:\n$goldText", + "// Ultra-light implementation:\n$ultraText" + ) + } + } + + private fun PsiAnnotation.renderAnnotation(): String { + + val renderedAttributes = parameterList.attributes.map { + val attributeValue = it.value?.renderAnnotationMemberValue() ?: "?" + + val name = when { + it.name === null && qualifiedName?.startsWith("java.lang.annotation.") == true -> "value" + else -> it.name + } + + if (name !== null) "$name = $attributeValue" else attributeValue + } + return "@$qualifiedName(${renderedAttributes.joinToString()})" + } + + + private fun PsiModifierListOwner.renderModifiers(typeIfApplicable: PsiType? = null): String { + val annotationsBuffer = mutableListOf() + for (annotation in annotations) { + if (annotation is KtLightNullabilityAnnotation<*> && skipRenderingNullability(typeIfApplicable)) { + continue + } + + annotationsBuffer.add( + annotation.renderAnnotation() + (if (this is PsiParameter) " " else "\n") + ) + } + annotationsBuffer.sort() + + val resultBuffer = StringBuffer(annotationsBuffer.joinToString(separator = "")) + for (modifier in PsiModifier.MODIFIERS.filter(::hasModifierProperty)) { + resultBuffer.append(modifier).append(" ") + } + return resultBuffer.toString() + } + + private fun PsiModifierListOwner.skipRenderingNullability(typeIfApplicable: PsiType?) = + isPrimitiveOrNonExisting(typeIfApplicable) || isPrivateOrParameterInPrivateMethod() + + private val NON_EXISTENT_QUALIFIED_CLASS_NAME = NON_EXISTENT_CLASS_NAME.replace("/", ".") + + private fun isPrimitiveOrNonExisting(typeIfApplicable: PsiType?): Boolean { + if (typeIfApplicable is PsiPrimitiveType) return true + if (typeIfApplicable?.getCanonicalText(false) == NON_EXISTENT_QUALIFIED_CLASS_NAME) return true + + return typeIfApplicable is PsiPrimitiveType + } + + private fun PsiType.renderType() = getCanonicalText(true) + + private fun PsiReferenceList?.renderRefList(keyword: String, sortReferences: Boolean = true): String { + if (this == null) return "" + + val references = referencedTypes + if (references.isEmpty()) return "" + + val referencesTypes = references.map { it.renderType() }.toTypedArray() + + if (sortReferences) referencesTypes.sort() + + return " " + keyword + " " + referencesTypes.joinToString() + } + + private fun PsiVariable.renderVar(): String { + var result = this.renderModifiers(type) + type.renderType() + " " + name + if (this is PsiParameter && this.isVarArgs) { + result += " /* vararg */" + } + + if (hasInitializer()) { + result += " = ${initializer?.text} /* initializer type: ${initializer?.type?.renderType()} */" + } + + computeConstantValue()?.let { result += " /* constant value $it */" } + + return result + } + + private fun Array.renderTypeParams() = + if (isEmpty()) "" + else "<" + joinToString { + val bounds = + if (it.extendsListTypes.isNotEmpty()) + " extends " + it.extendsListTypes.joinToString(" & ", transform = { it.renderType() }) + else "" + it.name!! + bounds + } + "> " + + private fun PsiAnnotationMemberValue.renderAnnotationMemberValue(): String = when (this) { + is KtLightPsiArrayInitializerMemberValue -> "{${initializers.joinToString { it.renderAnnotationMemberValue() }}}" + is PsiAnnotation -> renderAnnotation() + else -> text + } + + private fun PsiMethod.renderMethod() = + renderModifiers(returnType) + + (if (isVarArgs) "/* vararg */ " else "") + + typeParameters.renderTypeParams() + + (returnType?.renderType() ?: "") + " " + + name + + "(" + parameterList.parameters.joinToString { it.renderModifiers(it.type) + it.type.renderType() } + ")" + + (this as? PsiAnnotationMethod)?.defaultValue?.let { " default " + it.renderAnnotationMemberValue() }.orEmpty() + + throwsList.referencedTypes.let { thrownTypes -> + if (thrownTypes.isEmpty()) "" + else " throws " + thrownTypes.joinToString { it.renderType() } + } + + ";" + + "// ${getSignature(PsiSubstitutor.EMPTY).renderSignature()}" + + private fun MethodSignature.renderSignature(): String { + val typeParams = typeParameters.renderTypeParams() + val paramTypes = parameterTypes.joinToString(prefix = "(", postfix = ")") { it.renderType() } + val name = if (isConstructor) ".ctor" else name + return "$typeParams $name$paramTypes" + } + + private fun PsiEnumConstant.renderEnumConstant(): String { + val initializingClass = initializingClass ?: return name + + return buildString { + appendLine("$name {") + append(initializingClass.renderMembers()) + append("}") + } + } + + fun PsiClass.renderClass(): String { + val classWord = when { + isAnnotationType -> "@interface" + isInterface -> "interface" + isEnum -> "enum" + else -> "class" + } + + return buildString { + append(renderModifiers()) + append("$classWord ") + append("$name /* $qualifiedName*/") + append(typeParameters.renderTypeParams()) + append(extendsList.renderRefList("extends")) + append(implementsList.renderRefList("implements")) + appendLine(" {") + + if (isEnum) { + append( + fields + .filterIsInstance() + .joinToString(",\n") { it.renderEnumConstant() }.prependDefaultIndent() + ) + append(";\n\n") + } + + append(renderMembers()) + append("}") + } + } + + private fun PsiClass.renderMembers(): String { + return buildString { + appendSorted( + fields + .filterNot { it is PsiEnumConstant } + .map { it.renderVar().prependDefaultIndent() + ";\n\n" } + ) + + appendSorted( + methods + .map { it.renderMethod().prependDefaultIndent() + "\n\n" } + ) + + appendSorted( + innerClasses.map { "class ${it.name} ...\n\n".prependDefaultIndent() } + ) + } + } + + private fun StringBuilder.appendSorted(list: List) { + append(list.sorted().joinToString("")) + } + + private fun String.prependDefaultIndent() = prependIndent(" ") + + private fun checkDescriptorLeakOnElement(element: PsiElement) { + DebugReflectionUtil.walkObjects( + 10, + mapOf(element to element.javaClass.name), + Any::class.java, + { true }, + PairProcessor { value, backLink -> + if (value is DeclarationDescriptor) { + assertFails { + """Leaked descriptor ${value.javaClass.name} in ${element.javaClass.name}\n$backLink""" + } + } + true + }) + } + + fun checkDescriptorsLeak(lightClass: KtLightClass) { + checkDescriptorLeakOnElement(lightClass) + lightClass.methods.forEach { + checkDescriptorLeakOnElement(it) + it.parameterList.parameters.forEach { parameter -> checkDescriptorLeakOnElement(parameter) } + } + lightClass.fields.forEach { checkDescriptorLeakOnElement(it) } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.203 new file mode 100644 index 00000000000..c7b43fef29c --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.203 @@ -0,0 +1,207 @@ +/* + * 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.refactoring.suggested + +import com.intellij.lang.Language +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.executeCommand +import com.intellij.openapi.fileTypes.FileType +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import com.intellij.refactoring.suggested.BaseSuggestedRefactoringChangeCollectorTest +import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter +import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature +import com.intellij.util.ThrowableRunnable +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.test.runTest + +class KotlinSuggestedRefactoringChangeCollectorTest : BaseSuggestedRefactoringChangeCollectorTest() { + override val fileType: FileType + get() = KotlinFileType.INSTANCE + + override val language: Language + get() = KotlinLanguage.INSTANCE + + override fun addDeclaration(file: PsiFile, text: String): KtNamedFunction { + val psiFactory = KtPsiFactory(project) + return (file as KtFile).add(psiFactory.createDeclaration(text)) as KtNamedFunction + } + + override fun Signature.presentation(labelForParameterId: (Any) -> String?): String { + return buildString { + append("fun ") + val receiverType = (additionalData as KotlinSignatureAdditionalData?)?.receiverType + if (receiverType != null) { + append(receiverType) + append(".") + } + append(name) + append("(") + parameters.joinTo(this, separator = ", ") { it.presentation(labelForParameterId(it.id)) } + append(")") + if (type != null) { + append(": ") + append(type) + } + } + } + + private fun Parameter.presentation(label: String?): String { + return buildString { + if (modifiers.isNotEmpty()) { + append(modifiers) + append(" ") + } + append(name) + append(": ") + append(type) + if (label != null) { + append(" (") + append(label) + append(")") + } + } + } + + private fun createType(text: String): KtTypeReference { + return KtPsiFactory(project).createType(text) + } + + private fun createParameter(text: String): KtParameter { + return KtPsiFactory(project).createParameter(text) + } + + fun testAddParameter() { + doTest( + "fun foo(p1: Int) {}", + { it.valueParameterList!!.addParameter(createParameter("p2: Int")) }, + expectedOldSignature = "fun foo(p1: Int)", + expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p2: Int (new))" + ) + } + + fun testRemoveParameter() { + doTest( + "fun foo(p1: Int, p2: Int) {}", + { it.valueParameterList!!.removeParameter(0) }, + expectedOldSignature = "fun foo(p1: Int, p2: Int)", + expectedNewSignature = "fun foo(p2: Int (initialIndex = 1))" + ) + } + + fun testChangeParameterType() { + doTest( + "fun foo(p1: Int, p2: Int) {}", + { it.valueParameters[1].typeReference = createType("Any?") }, + expectedOldSignature = "fun foo(p1: Int, p2: Int)", + expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p2: Any? (initialIndex = 1))" + ) + } + + fun testChangeParameterNames() { + doTest( + "fun foo(p1: Int, p2: Int) {}", + { it.valueParameters[0].setName("newP1") }, + { it.valueParameters[1].setName("newP2") }, + expectedOldSignature = "fun foo(p1: Int, p2: Int)", + expectedNewSignature = "fun foo(newP1: Int (initialIndex = 0), newP2: Int (initialIndex = 1))" + ) + } + + fun testReplaceParameter() { + doTest( + "fun foo(p1: Int, p2: Int) {}", + { it.valueParameters[0].replace(createParameter("newP1: Long")) }, + expectedOldSignature = "fun foo(p1: Int, p2: Int)", + expectedNewSignature = "fun foo(newP1: Long (new), p2: Int (initialIndex = 1))" + ) + } + + fun testReorderParametersChangeTypesAndNames() { + doTest( + "fun foo(p1: Int, p2: Int, p3: Int) {}", + { + editor.caretModel.moveToOffset(it.valueParameters[2].textOffset) + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) + }, + { + executeCommand { + runWriteAction { + it.valueParameters[0].typeReference = createType("Any?") + it.valueParameters[1].typeReference = createType("Long") + it.valueParameters[2].typeReference = createType("Double") + } + } + }, + { + executeCommand { + runWriteAction { + it.valueParameters[1].setName("newName") + } + } + }, + wrapIntoCommandAndWriteAction = false, + expectedOldSignature = "fun foo(p1: Int, p2: Int, p3: Int)", + expectedNewSignature = "fun foo(p3: Any? (initialIndex = 2), newName: Long (initialIndex = 0), p2: Double (initialIndex = 1))" + ) + } + + fun testReorderParametersByCutPaste() { + doTest( + "fun foo(p1: Int, p2: String, p3: Char)", + { + val offset = it.valueParameters[1].textRange.endOffset + editor.caretModel.moveToOffset(offset) + editor.selectionModel.setSelection(offset, offset + ", p3: Char".length) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_CUT) + }, + { + val offset = it.valueParameters[0].textRange.endOffset + editor.caretModel.moveToOffset(offset) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE) + }, + wrapIntoCommandAndWriteAction = false, + expectedOldSignature = "fun foo(p1: Int, p2: String, p3: Char)", + expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p3: Char (initialIndex = 2), p2: String (initialIndex = 1))" + ) + } + + fun testReorderParametersByCutPasteAfterChangingName() { + doTest( + "fun foo(p1: Int, p2: String, p3: Char)", + { + executeCommand { + runWriteAction { + it.valueParameters[2].setName("p3New") + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + } + } + }, + { + val offset = it.valueParameters[1].textRange.endOffset + editor.caretModel.moveToOffset(offset) + editor.selectionModel.setSelection(offset, offset + ", p3New: Char".length) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_CUT) + }, + { + val offset = it.valueParameters[0].textRange.endOffset + editor.caretModel.moveToOffset(offset) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE) + }, + wrapIntoCommandAndWriteAction = false, + expectedOldSignature = "fun foo(p1: Int, p2: String, p3: Char)", + expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p3New: Char (initialIndex = 2), p2: String (initialIndex = 1))" + ) + } + + override fun runTestRunnable(testRunnable: ThrowableRunnable) { + runTest { super.runTestRunnable(testRunnable) } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.203 new file mode 100644 index 00000000000..8cb593ab864 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.203 @@ -0,0 +1,401 @@ +/* + * 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.refactoring.suggested + +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.executeCommand +import com.intellij.openapi.fileTypes.FileType +import com.intellij.refactoring.suggested.BaseSuggestedRefactoringChangeListenerTest +import com.intellij.util.ThrowableRunnable +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.kotlin.test.runTest + +class KotlinSuggestedRefactoringChangeListenerTest : BaseSuggestedRefactoringChangeListenerTest() { + override val fileType: FileType + get() = KotlinFileType.INSTANCE + + fun test1() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p") } + + perform("nextSignature: 'foo(p)'") { commitAll() } + perform { myFixture.type(":") } + perform { myFixture.type(" S") } + perform { myFixture.type("tr") } + perform("nextSignature: 'foo(p: Str)'") { commitAll() } + perform { myFixture.type("ing") } + perform("nextSignature: 'foo(p: String)'") { commitAll() } + + perform { + perform { myFixture.type(", ") } + commitAll() + } + } + + fun testCompletion() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p: DoubleArra") } + perform("nextSignature: 'foo(p: DoubleArra)'", "nextSignature: 'foo(p: DoubleArray)'") { myFixture.completeBasic() } + } + + fun testChangeOutsideSignature() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p: A") } + perform("reset") { + insertString(editor.document.textLength, "\nval") + } + } + + fun testEditOtherSignature() { + setup("fun foo() {}\nfun bar() = 0") + + val otherFunction = (file as KtFile).declarations[1] as KtNamedFunction + val offset = otherFunction.valueParameterList!!.startOffset + 1 + val marker = editor.document.createRangeMarker(offset, offset) + + perform("editingStarted: 'foo()'") { myFixture.type("p: A") } + perform("nextSignature: 'foo(p: A)'") { commitAll() } + + perform("reset", "editingStarted: 'bar()'", "nextSignature: 'bar(p1: String)'") { + assert(marker.isValid) + insertString(marker.startOffset, "p1: String") + commitAll() + } + } + + fun testChangeInAnotherFile() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p: A") } + perform("reset") { + setup("") + myFixture.type(" ") + } + } + + fun testAddImport() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'", "nextSignature: 'foo(p: Any)'") { + myFixture.type("p: Any") + commitAll() + } + perform("nextSignature: 'foo(p: Any)'", "nextSignature: 'foo(p: Any)'") { + addImport("java.util.ArrayList") + } + perform("nextSignature: 'foo(p: Any, p2: String)'") { + myFixture.type(", p2: String") + commitAll() + } + perform("nextSignature: 'foo(p: Any, p2: String)'", "nextSignature: 'foo(p: Any, p2: String)'") { + addImport("java.util.Date") + } + } + + fun testAddImportWithBlankLineInsertion() { + setup( + """ + import foo.bar + fun foo() {} + """.trimIndent() + ) + + perform("editingStarted: 'foo()'", "nextSignature: 'foo(p: ArrayList)'") { + myFixture.type("p: ArrayList") + commitAll() + } + perform("nextSignature: 'foo(p: ArrayList)'", "nextSignature: 'foo(p: ArrayList)'") { + addImport("java.util.ArrayList") + } + perform("nextSignature: 'foo(p: ArrayList)'") { + myFixture.type("") + commitAll() + } + perform("nextSignature: 'foo(p: ArrayList, p2: Any)'") { + myFixture.type(", p2: Any") + commitAll() + } + } + + fun testAddImportWithBlankLinesRemoval() { + setup( + """ + import foo.bar + + + + fun foo() {} + """.trimIndent() + ) + + perform("editingStarted: 'foo()'", "nextSignature: 'foo(p: ArrayList)'") { + myFixture.type("p: ArrayList") + commitAll() + } + perform("nextSignature: 'foo(p: ArrayList)'", "nextSignature: 'foo(p: ArrayList)'") { + addImport("java.util.ArrayList") + } + perform("nextSignature: 'foo(p: ArrayList)'") { + myFixture.type("") + commitAll() + } + perform("nextSignature: 'foo(p: ArrayList, p2: Any)'") { + myFixture.type(", p2: Any") + commitAll() + } + } + + fun testReorderParameters() { + setup("fun foo(p1: String, p2: Any, p3: Int) {}") + + perform("editingStarted: 'foo(p1: String, p2: Any, p3: Int)'") { + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) + } + perform("nextSignature: 'foo(p1: String, p3: Int, p2: Any)'") { + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) + } + perform("nextSignature: 'foo(p3: Int, p1: String, p2: Any)'") { + commitAll() + } + perform("nextSignature: 'foo(p1: String, p3: Int, p2: Any)'") { + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_RIGHT) + commitAll() + } + } + + fun testAddParameterViaPsi() { + setup("fun foo(p1: Int) {}") + + val function = (file as KtFile).declarations.single() as KtFunction + perform( + "editingStarted: 'foo(p1: Int)'", + "nextSignature: 'foo(p1: Int,)'", + "nextSignature: 'foo(p1: Int,p2: Int)'", + "nextSignature: 'foo(p1: Int, p2: Int)'" + ) { + executeCommand { + runWriteAction { + function.valueParameterList!!.addParameter(KtPsiFactory(project).createParameter("p2: Int")) + } + } + } + } + + fun testCommentTyping() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'", "nextSignature: 'foo(p1: Any)'") { + myFixture.type("p1: Any") + commitAll() + } + + perform { + myFixture.type("/*") + commitAll() + } + + perform { + myFixture.type(" this is comment for parameter") + commitAll() + } + + perform("nextSignature: 'foo(p1: Any/* this is comment for parameter*/)'") { + myFixture.type("*/") + commitAll() + } + + perform { + myFixture.type(", p2: Int /*") + commitAll() + } + + perform { + myFixture.type("this is comment for another parameter") + commitAll() + } + + perform("nextSignature: 'foo(p1: Any/* this is comment for parameter*/, p2: Int /*this is comment for another parameter*/)'") { + myFixture.type("*/") + commitAll() + } + } + + fun testAddReturnType() { + setup( + """ + interface I { + fun foo() + } + """.trimIndent() + ) + + perform("editingStarted: 'foo()'") { myFixture.type(": String") } + + perform("nextSignature: 'foo(): String'") { commitAll() } + } + + fun testNewLocal() { + setup( + """ + fun foo() { + + print(a) + } + """.trimIndent() + ) + + perform { + myFixture.type("val a") + commitAll() + myFixture.type("bcd") + commitAll() + } + } + + fun testNewFunction() { + setup( + """ + interface I { + + } + """.trimIndent() + ) + + perform { + myFixture.type("fun foo_bar123(_p1: Int)") + commitAll() + } + } + + fun testNewProperty() { + setup( + """ + interface I { + + } + """.trimIndent() + ) + + perform { + myFixture.type("val prop: I") + commitAll() + + myFixture.type("nt") + commitAll() + } + } + + fun testNewLocalWithNewUsage() { + setup( + """ + fun foo() { + + } + """.trimIndent() + ) + + perform { + myFixture.type("val a = 10") + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_ENTER) + myFixture.type("print(a)") + commitAll() + } + + perform("editingStarted: 'a'", "nextSignature: 'abcd'") { + val variable = file.findDescendantOfType()!! + myFixture.editor.caretModel.moveToOffset(variable.nameIdentifier!!.endOffset) + myFixture.type("bcd") + commitAll() + } + } + + fun testNewLocalBeforeExpression() { + setup( + """ + fun foo(p: Int) { + p * p + } + """.trimIndent() + ) + + perform { + myFixture.type("val a") + commitAll() + } + perform { + myFixture.type("bcd = ") + commitAll() + } + } + + fun testNewClassWithConstructor() { + setup("") + + perform { + myFixture.type("class C") + commitAll() + } + perform { + myFixture.type("(p: Int)") + commitAll() + } + } + + fun testNewSecondaryConstructor() { + setup( + """ + class C { + + } + """.trimIndent() + ) + + perform { + myFixture.type("constructor(p1: Int)") + commitAll() + } + perform { + myFixture.type("(, p2: String)") + commitAll() + } + } + + fun testRenameComponentVar() { + setup( + """ + fun f() { + val (a, b) = f() + } + """.trimIndent() + ) + + perform("editingStarted: 'a'", "nextSignature: 'newa'") { + myFixture.type("new") + commitAll() + } + } + + override fun runTestRunnable(testRunnable: ThrowableRunnable) { + runTest { super.runTestRunnable(testRunnable) } + } + + private fun addImport(fqName: String) { + executeCommand { + runWriteAction { + (file as KtFile).importList!!.add(KtPsiFactory(project).createImportDirective(ImportPath.fromString(fqName))) + } + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.203 new file mode 100644 index 00000000000..2112f4904c8 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.203 @@ -0,0 +1,1589 @@ +/* + * 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.refactoring.suggested + +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.executeCommand +import com.intellij.openapi.fileTypes.LanguageFileType +import com.intellij.psi.PsiDocumentManager +import com.intellij.refactoring.suggested.BaseSuggestedRefactoringTest +import com.intellij.refactoring.suggested.SuggestedRefactoringExecution +import com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl +import com.intellij.refactoring.suggested._suggestedChangeSignatureNewParameterValuesForTests +import com.intellij.util.ThrowableRunnable +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.ImportPath +import org.jetbrains.kotlin.test.runTest + +class KotlinSuggestedRefactoringTest : BaseSuggestedRefactoringTest() { + override val fileType: LanguageFileType + get() = KotlinFileType.INSTANCE + + override fun setUp() { + super.setUp() + _suggestedChangeSignatureNewParameterValuesForTests = { + SuggestedRefactoringExecution.NewParameterValue.Expression(KtPsiFactory(project).createExpression("default$it")) + } + } + + override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE + + fun testAddParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + fun foo(p1: Int) { + foo(1) + } + + fun bar() { + foo(2) + } + """.trimIndent(), + """ + fun foo(p1: Int, p2: Any) { + foo(1, default0) + } + + fun bar() { + foo(2, default0) + } + """.trimIndent(), + "usages", + { myFixture.type(", p2: Any") } + ) + } + + fun testRemoveParameter() { + doTestChangeSignature( + """ + fun foo(p1: Any?, p2: Int) { + foo(null, 1) + } + + fun bar() { + foo(1, 2) + } + """.trimIndent(), + """ + fun foo(p1: Any?) { + foo(null) + } + + fun bar() { + foo(1) + } + """.trimIndent(), + "usages", + { + deleteTextBeforeCaret(", p2: Int") + } + ) + } + + fun testReorderParameters1() { + doTestChangeSignature( + """ + fun foo(p1: Any?, p2: Int, p3: Boolean) { + foo(null, 1, true) + } + + fun bar() { + foo(1, 2, false) + } + """.trimIndent(), + """ + fun foo(p3: Boolean, p1: Any?, p2: Int) { + foo(true, null, 1) + } + + fun bar() { + foo(false, 1, 2) + } + """.trimIndent(), + "usages", + { myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) }, + { myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testReorderParameters2() { + doTestChangeSignature( + """ + fun foo(p1: Any?, p2: Int, p3: Boolean) { + foo(null, 1, true) + } + + fun bar() { + foo(1, 2, false) + } + """.trimIndent(), + """ + fun foo(p2: Int, p1: Any?, p3: Boolean) { + foo(1, null, true) + } + + fun bar() { + foo(2, 1, false) + } + """.trimIndent(), + "usages", + { + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_CUT) + }, + { + editor.caretModel.moveToOffset(editor.caretModel.offset - 10) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE) + }, + { + myFixture.type(", ") + }, + { + editor.caretModel.moveToOffset(editor.caretModel.offset + 10) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) + }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testChangeParameterType() { + doTestChangeSignature( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: Any) + } + + class C : I { + override fun foo(p: Any) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testChangeParameterTypeExpectFunction() { + ignoreErrorsBefore = true + ignoreErrorsAfter = true + doTestChangeSignature( + "expect fun foo(p: String)", + "expect fun foo(p: Any)", + "actual declarations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testUnresolvedParameterType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: XXX) + } + + class C : I { + override fun foo(p: XXX) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "XXX") + } + ) + } + + fun testChangeParameterTypeWithImportInsertion() { + myFixture.addFileToProject( + "X.kt", + """ + package xxx + class X + """.trimIndent() + ) + + val otherFile = myFixture.addFileToProject( + "Other.kt", + """ + class D : I { + override fun foo(p: String) { + } + } + """.trimIndent() + ) + + doTestChangeSignature( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + } + } + """.trimIndent(), + """ + import xxx.X + + interface I { + fun foo(p: X) + } + + class C : I { + override fun foo(p: X) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "X") + }, + { + addImport("xxx.X") + } + ) + + assertEquals( + """ + import xxx.X + + class D : I { + override fun foo(p: X) { + } + } + """.trimIndent(), + otherFile.text + ) + } + + fun testChangeReturnType() { + doTestChangeSignature( + """ + interface I { + fun foo(): String + } + + class C : I { + override fun foo(): String = "" + } + """.trimIndent(), + """ + interface I { + fun foo(): Any + } + + class C : I { + override fun foo(): Any = "" + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testAddReturnType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() {} + } + """.trimIndent(), + """ + interface I { + fun foo(): String + } + + class C : I { + override fun foo(): String {} + } + """.trimIndent(), + "implementations", + { + myFixture.type(": String") + } + ) + } + + fun testRemoveReturnType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo(): String + } + + class C : I { + override fun foo(): String = "" + } + """.trimIndent(), + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() = "" + } + """.trimIndent(), + "implementations", + { + deleteTextAtCaret(": String") + } + ) + } + + fun testRenameAndAddReturnType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() {} + } + """.trimIndent(), + """ + interface I { + fun fooNew(): String + } + + class C : I { + override fun fooNew(): String {} + } + """.trimIndent(), + "usages", + { + val offset = editor.caretModel.offset + editor.document.insertString(offset, "New") + }, + { + val offset = editor.caretModel.offset + editor.document.insertString(offset + "New()".length, ": String") + } + ) + } + + fun testChangeParameterTypeWithImportReplaced() { + myFixture.addFileToProject( + "XY.kt", + """ + package xxx + class X + class Y + """.trimIndent() + ) + + val otherFile = myFixture.addFileToProject( + "Other.kt", + """ + import xxx.X + + class D : I { + override fun foo(p: X) { + } + } + """.trimIndent() + ) + + doTestChangeSignature( + """ + import xxx.X + + interface I { + fun foo(p: X) + } + """.trimIndent(), + """ + import xxx.Y + + interface I { + fun foo(p: Y) + } + """.trimIndent(), + "implementations", + { + val offset = editor.caretModel.offset + editor.document.replaceString(offset, offset + "X".length, "Y") + }, + { + addImport("xxx.Y") + removeImport("xxx.X") + } + ) + + assertEquals( + """ + import xxx.Y + + class D : I { + override fun foo(p: Y) { + } + } + """.trimIndent(), + otherFile.text + ) + } + + fun testPreserveCommentsAndFormatting() { + doTestChangeSignature( + """ + class A : I { + override fun foo() { + } + } + + interface I { + fun foo() + } + """.trimIndent(), + """ + class A : I { + override fun foo(p1: Int, p2: Long, p3: Any?) { + } + } + + interface I { + fun foo(p1: Int/*comment 1*/, p2: Long/*comment 2*/, + p3: Any?/*comment 3*/) + } + """.trimIndent(), + "usages", + { + myFixture.type("p1: Int/*comment 1*/") + }, + { + myFixture.type(", p2: Long/*comment 2*/") + }, + { + myFixture.type(",") + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_ENTER) + myFixture.type("p3: Any?/*comment 3*/") + }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testParameterCompletion() { + ignoreErrorsAfter = true + doTestChangeSignature( + "package ppp\nclass Abcdef\nfun foo(p: Int) { }\nfun bar() { foo(1) }", + "package ppp\nclass Abcdef\nfun foo(abcdef: Abcdef, p: Int) { }\nfun bar() { foo(default0, 1) }", + "usages", + { + executeCommand { + runWriteAction { + myFixture.type("abcde") + PsiDocumentManager.getInstance(project).commitAllDocuments() + } + } + }, + { + myFixture.completeBasic() + }, + { + myFixture.finishLookup('\n') + }, + { + executeCommand { + runWriteAction { + myFixture.type(", ") + PsiDocumentManager.getInstance(project).commitAllDocuments() + } + } + }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testRenameTwoParameters() { + doTestChangeSignature( + """ + fun foo(p1: Int, p2: String) { + p1.hashCode() + p2.hashCode() + } + """.trimIndent(), + """ + fun foo(p1New: Int, p2New: String) { + p1New.hashCode() + p2New.hashCode() + } + """.trimIndent(), + "usages", + { + val function = (file as KtFile).declarations.single() as KtFunction + function.valueParameters[0].setName("p1New") + function.valueParameters[1].setName("p2New") + } + ) + } + + fun testChangePropertyType() { + doTestChangeSignature( + """ + interface I { + var v: String + } + + class C : I { + override var v: String = "" + } + """.trimIndent(), + """ + interface I { + var v: Any + } + + class C : I { + override var v: Any = "" + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + }, + expectedPresentation = """ + Old: + 'var ' + 'v' + ': ' + 'String' (modified) + New: + 'var ' + 'v' + ': ' + 'Any' (modified) + """.trimIndent() + ) + } + + fun testRenameClass() { + doTestRename( + """ + var v: C? = null + + interface C + """.trimIndent(), + """ + var v: CNew? = null + + interface CNew + """.trimIndent(), + "C", + "CNew", + { + myFixture.type("New") + } + ) + } + + fun testRenameLocalVar() { + doTestRename( + """ + fun foo() { + var v = 0 + v++ + } + """.trimIndent(), + """ + fun foo() { + var vNew = 0 + vNew++ + } + """.trimIndent(), + "v", + "vNew", + { + myFixture.type("New") + } + ) + } + + fun testRenameComponentVar() { + doTestRename( + """ + data class Pair(val t1: T1, val t2: T2) + + fun f(): Pair = Pair("a", 1) + + fun g() { + val (a, b) = f() + b.hashCode() + } + """.trimIndent(), + """ + data class Pair(val t1: T1, val t2: T2) + + fun f(): Pair = Pair("a", 1) + + fun g() { + val (a, b1) = f() + b1.hashCode() + } + """.trimIndent(), + "b", + "b1", + { + myFixture.type("1") + } + ) + } + + fun testRenameLoopVar() { + doTestRename( + """ + fun f(list: List) { + for (s in list) { + s.hashCode() + } + } + """.trimIndent(), + """ + fun f(list: List) { + for (s1 in list) { + s1.hashCode() + } + } + """.trimIndent(), + "s", + "s1", + { + myFixture.type("1") + } + ) + } + + fun testRenameParameter() { + doTestRename( + """ + fun foo(p: String) { + p.hashCode() + } + """.trimIndent(), + """ + fun foo(pNew: String) { + pNew.hashCode() + } + """.trimIndent(), + "p", + "pNew", + { + myFixture.type("New") + } + ) + } + + fun testRenameParametersInOverrides() { + doTestRename( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + p.hashCode() + } + } + """.trimIndent(), + """ + interface I { + fun foo(pNew: String) + } + + class C : I { + override fun foo(pNew: String) { + pNew.hashCode() + } + } + """.trimIndent(), + "p", + "pNew", + { + myFixture.type("New") + } + ) + } + + fun testAddPrimaryConstructorParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + class C(private val x: String, y: Int) + + fun foo() { + val c = C("a", 1) + } + """.trimIndent(), + """ + class C(private val x: String, y: Int, z: Any) + + fun foo() { + val c = C("a", 1, default0) + } + """.trimIndent(), + "usages", + { myFixture.type(", z: Any") }, + expectedPresentation = """ + Old: + 'C' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + ',' + LineBreak(' ', true) + Group: + 'y' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'C' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + ',' + LineBreak(' ', true) + Group: + 'y' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group (added): + 'z' + ': ' + 'Any' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testAddSecondaryConstructorParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + class C { + constructor(x: String) + } + + fun foo() { + val c = C("a") + } + """.trimIndent(), + """ + class C { + constructor(x: String, y: Int) + } + + fun foo() { + val c = C("a", default0) + } + """.trimIndent(), + "usages", + { myFixture.type(", y: Int") }, + expectedPresentation = """ + Old: + 'constructor' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + LineBreak('', false) + ')' + New: + 'constructor' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + ',' + LineBreak(' ', true) + Group (added): + 'y' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testAddReceiver() { + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() { + } + } + """.trimIndent(), + """ + interface I { + fun Int.foo() + } + + class C : I { + override fun Int.foo() { + } + } + """.trimIndent(), + "usages", + { myFixture.type("Int.") }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', false) + ')' + New: + 'fun ' + 'Int.' (added) + 'foo' + '(' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testAddReceiverAndParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + fun foo() { + } + + fun bar() { + foo() + } + """.trimIndent(), + """ + fun Int.foo(o: Any) { + } + + fun bar() { + default0.foo(default1) + } + """.trimIndent(), + "usages", + { myFixture.type("Int.") }, + { repeat("foo(".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT) } }, + { myFixture.type("o: Any") } + ) + } + + fun testRemoveReceiver() { + doTestChangeSignature( + """ + fun Int.foo() { + } + + fun bar() { + 1.foo() + } + """.trimIndent(), + """ + fun foo() { + } + + fun bar() { + foo() + } + """.trimIndent(), + "usages", + { + repeat(4) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + } + ) + } + + fun testChangeReceiverType() { + doTestChangeSignature( + """ + interface I { + fun Int.foo() + } + + class C : I { + override fun Int.foo() { + } + } + + fun I.f() { + 1.foo() + } + """.trimIndent(), + """ + interface I { + fun Any.foo() + } + + class C : I { + override fun Any.foo() { + } + } + + fun I.f() { + 1.foo() + } + """.trimIndent(), + "implementations", + { + repeat("Int".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + myFixture.type("Any") + }, + expectedPresentation = """ + Old: + 'fun ' + 'Int' (modified) + '.' + 'foo' + '(' + LineBreak('', false) + ')' + New: + 'fun ' + 'Any' (modified) + '.' + 'foo' + '(' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testChangeReceiverTypeAndRemoveParameter() { + doTestChangeSignature( + """ + fun Int.foo(p: Any) {} + + fun bar() { + 1.foo("a") + } + """.trimIndent(), + """ + fun Any.foo() {} + + fun bar() { + 1.foo() + } + """.trimIndent(), + "usages", + { + repeat("Int".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + myFixture.type("Any") + }, + { + editor.caretModel.moveToOffset(editor.caretModel.offset + ".foo(".length) + repeat("p: Any".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + } + ) + } + + fun testAddVarargParameter() { + doTestChangeSignature( + """ + interface I { + fun foo(p: Int) + } + + class C : I { + override fun foo(p: Int) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: Int, vararg s: String) + } + + class C : I { + override fun foo(p: Int, vararg s: String) { + } + } + """.trimIndent(), + "usages", + { myFixture.type(", vararg s: String") }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group (added): + 'vararg' + ' ' + 's' + ': ' + 'String' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + //TODO +/* + fun testAddVarargModifier() { + doTestChangeSignature( + """ + interface I { + fun foo(p: Int) + } + + class C : I { + override fun foo(p: Int) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(vararg p: Int) + } + + class C : I { + override fun foo(vararg p: Int) { + } + } + """.trimIndent(), + { myFixture.type("vararg ") }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'vararg' (added) + ' ' + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testRemoveVarargModifier() { + doTestChangeSignature( + """ + interface I { + fun foo(vararg p: Int) + } + + class C : I { + override fun foo(vararg p: Int) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: Int) + } + + class C : I { + override fun foo(p: Int) { + } + } + """.trimIndent(), + { + deleteStringAtCaret("vararg ") + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'vararg' (removed) + ' ' + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } +*/ + + fun testSwapConstructorParameters() { + doTestChangeSignature( + """ + class C( + p1: Int, + p2: String + ) + + fun foo() { + C(1, "") + } + """.trimIndent(), + """ + class C( + p2: String, + p1: Int + ) + + fun foo() { + C("", 1) + } + """.trimIndent(), + "usages", + { + myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION) + } + ) + } + + fun testChangeParameterTypeOfVirtualExtensionMethod() { + doTestChangeSignature( + """ + abstract class Base { + protected abstract fun Int.foo(p: String) + + fun bar() { + 1.foo("a") + } + } + + class Derived : Base() { + override fun Int.foo(p: String) { + } + } + """.trimIndent(), + """ + abstract class Base { + protected abstract fun Int.foo(p: Any) + + fun bar() { + 1.foo("a") + } + } + + class Derived : Base() { + override fun Int.foo(p: Any) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testAddParameterToVirtualExtensionMethod() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + abstract class Base { + protected abstract fun Int.foo(p: String) + + fun bar() { + 1.foo("a") + } + } + + class Derived : Base() { + override fun Int.foo(p: String) { + } + } + """.trimIndent(), + """ + abstract class Base { + protected abstract fun Int.foo(p: String, p1: Int) + + fun bar() { + 1.foo("a", default0) + } + } + + class Derived : Base() { + override fun Int.foo(p: String, p1: Int) { + } + } + """.trimIndent(), + "usages", + { + myFixture.type(", p1: Int") + } + ) + } + + fun testAddParameterWithFullyQualifiedType() { + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() { + } + } + """.trimIndent(), + """ + import java.io.InputStream + + interface I { + fun foo(p: java.io.InputStream) + } + + class C : I { + override fun foo(p: InputStream) { + } + } + """.trimIndent(), + "usages", + { myFixture.type("p: java.io.InputStream") } + ) + } + + fun testAddOptionalParameter() { + doTestChangeSignature( + """ + interface I { + fun foo(p1: Int) + } + + class C : I { + override fun foo(p1: Int) { + } + } + + fun f(i: I) { + i.foo(1) + } + """.trimIndent(), + """ + interface I { + fun foo(p1: Int, p2: Int = 10) + } + + class C : I { + override fun foo(p1: Int, p2: Int) { + } + } + + fun f(i: I) { + i.foo(1) + } + """.trimIndent(), + "implementations", + { + myFixture.type(", p2: Int = 10") + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p1' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p1' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group (added): + 'p2' + ': ' + 'Int' + ' = ' + '10' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testReorderOptionalParameter() { + doTestChangeSignature( + """ + interface I { + fun foo(p1: Int, p2: Int = 10) + } + + class C : I { + override fun foo(p1: Int, p2: Int) { + } + } + + fun f(i: I) { + i.foo(2) + } + """.trimIndent(), + """ + interface I { + fun foo(p2: Int = 10, p1: Int) + } + + class C : I { + override fun foo(p2: Int, p1: Int) { + } + } + + fun f(i: I) { + i.foo(p1 = 2) + } + """.trimIndent(), + "usages", + { + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_RIGHT) + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group (moved): + 'p1' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group: + 'p2' + ': ' + 'Int' + ' = ' + '10' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p2' + ': ' + 'Int' + ' = ' + '10' + ',' + LineBreak(' ', true) + Group (moved): + 'p1' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testReplaceTypeWithItsAlias() { + doTestChangeSignature( + """ + typealias StringToUnit = (String) -> Unit + + interface I { + fun foo(): (String) -> Unit + } + + class C : I { + override fun foo(): (String) -> Unit { + throw UnsupportedOperationException() + } + } + """.trimIndent(), + """ + typealias StringToUnit = (String) -> Unit + + interface I { + fun foo(): StringToUnit + } + + class C : I { + override fun foo(): StringToUnit { + throw UnsupportedOperationException() + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("(String) -> Unit", "StringToUnit") + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', false) + ')' + ': ' + '(String) -> Unit' (modified) + New: + 'fun ' + 'foo' + '(' + LineBreak('', false) + ')' + ': ' + 'StringToUnit' (modified) + """.trimIndent() + ) + } + + fun testNewParameterValueReferencesAnotherParameter() { + _suggestedChangeSignatureNewParameterValuesForTests = { + val declaration = SuggestedRefactoringProviderImpl.getInstance(project).state!!.declaration + val codeFragment = KtPsiFactory(project).createExpressionCodeFragment("p1 * p1", declaration) + SuggestedRefactoringExecution.NewParameterValue.Expression(codeFragment.getContentElement()!!) + } + doTestChangeSignature( + """ + fun foo(p1: Int) { + } + + fun bar() { + foo(1) + foo(2) + } + """.trimIndent(), + """ + fun foo(p1: Int, p2: Int) { + } + + fun bar() { + foo(1, 1 * 1) + foo(2, 2 * 2) + } + """.trimIndent(), + "usages", + { myFixture.type(", p2: Int") } + ) + } + + override fun runTestRunnable(testRunnable: ThrowableRunnable) { + runTest { super.runTestRunnable(testRunnable) } + } + + private fun addImport(fqName: String) { + (file as KtFile).importList!!.add(KtPsiFactory(project).createImportDirective(ImportPath.fromString(fqName))) + } + + private fun removeImport(fqName: String) { + (file as KtFile).importList!!.imports.first { it.importedFqName?.asString() == fqName }.delete() + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationTest.kt.203 b/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationTest.kt.203 new file mode 100644 index 00000000000..c57049f3ed2 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/script/AbstractScriptConfigurationTest.kt.203 @@ -0,0 +1,373 @@ +/* + * 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.script + +import com.intellij.openapi.module.JavaModuleType +import com.intellij.openapi.module.Module +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.testFramework.PlatformTestCase +import com.intellij.testFramework.PsiTestUtil +import com.intellij.util.ui.UIUtil +import org.jdom.Element +import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime +import org.jetbrains.kotlin.idea.completion.test.KotlinCompletionTestCase +import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink +import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager.Companion.updateScriptDependenciesSynchronously +import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionContributor +import org.jetbrains.kotlin.idea.core.script.ScriptDefinitionsManager +import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings +import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingUtil +import org.jetbrains.kotlin.idea.script.AbstractScriptConfigurationTest.Companion.useDefaultTemplate +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.idea.util.projectStructure.getModuleDir +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.scripting.definitions.findScriptDefinition +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.MockLibraryUtil +import org.jetbrains.kotlin.test.TestJdkKind +import org.jetbrains.kotlin.test.TestMetadata +import org.jetbrains.kotlin.test.util.addDependency +import org.jetbrains.kotlin.test.util.projectLibrary +import org.jetbrains.kotlin.utils.PathUtil +import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_JAVA_SCRIPT_RUNTIME_JAR +import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_COMMON_JAR +import org.jetbrains.kotlin.utils.PathUtil.KOTLIN_SCRIPTING_JVM_JAR +import java.io.File +import java.util.regex.Pattern +import kotlin.reflect.full.findAnnotation +import kotlin.script.dependencies.Environment +import kotlin.script.experimental.api.ScriptDiagnostic + +// some bugs can only be reproduced when some module and script have intersecting library dependencies +private const val configureConflictingModule = "// CONFLICTING_MODULE" + +private fun String.splitOrEmpty(delimeters: String) = split(delimeters).takeIf { it.size > 1 } ?: emptyList() +internal val switches = listOf( + useDefaultTemplate, + configureConflictingModule +) + +abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() { + companion object { + private const val SCRIPT_NAME = "script.kts" + + val validKeys = setOf("javaHome", "sources", "classpath", "imports", "template-classes-names") + const val useDefaultTemplate = "// DEPENDENCIES:" + const val templatesSettings = "// TEMPLATES: " + } + + protected fun testDataFile(fileName: String): File = File(testDataPath, fileName) + + protected fun testDataFile(): File = testDataFile(fileName()) + + protected fun testPath(fileName: String = fileName()): String = testDataFile(fileName).toString() + + protected fun testPath(): String = testPath(fileName()) + + protected open fun fileName(): String = KotlinTestUtils.getTestDataFileName(this::class.java, this.name) ?: (getTestName(false) + ".kt") + + override fun getTestDataPath(): String { + return this::class.findAnnotation()?.value ?: super.getTestDataPath() + } + + override fun setUpModule() { + // do not create default module + } + + private fun findMainScript(testDir: String): File { + val scriptFile = File(testDir).walkTopDown().find { it.name == SCRIPT_NAME } + if (scriptFile != null) return scriptFile + + return File(testDir).walkTopDown().singleOrNull { it.name.contains("script") } + ?: error("Couldn't find $SCRIPT_NAME file in $testDir") + } + + private val sdk by lazy { + runWriteAction { + val sdk = PluginTestCaseBase.addJdk(testRootDisposable) { PluginTestCaseBase.jdk(TestJdkKind.MOCK_JDK) } + ProjectRootManager.getInstance(project).projectSdk = sdk + sdk + } + } + + protected fun configureScriptFile(path: String): VirtualFile { + val mainScriptFile = findMainScript(path) + return configureScriptFile(path, mainScriptFile) + } + + protected fun configureScriptFile(path: String, mainScriptFile: File): VirtualFile { + val environment = createScriptEnvironment(mainScriptFile) + registerScriptTemplateProvider(environment) + + File(path, "mainModule").takeIf { it.exists() }?.let { + myModule = createTestModuleFromDir(it) + } + + File(path).listFiles { file -> file.name.startsWith("module") }.filter { it.exists() }.forEach { + val newModule = createTestModuleFromDir(it) + assert(myModule != null) { "Main module should exists" } + ModuleRootModificationUtil.addDependency(myModule, newModule) + } + + if (module != null) { + module.addDependency( + projectLibrary( + "script-runtime", + classesRoot = VfsUtil.findFileByIoFile(PathUtil.kotlinPathsForDistDirectory.scriptRuntimePath, true) + ) + ) + + if (environment["template-classes"] != null) { + module.addDependency( + projectLibrary( + "script-template-library", + classesRoot = VfsUtil.findFileByIoFile(environment["template-classes"] as File, true) + ) + ) + } + } + + if (configureConflictingModule in environment) { + val sharedLib = VfsUtil.findFileByIoFile(environment["lib-classes"] as File, true)!! + if (module == null) { + // Force create module if it doesn't exist + myModule = createTestModuleByName("mainModule") + } + module.addDependency(projectLibrary("sharedLib", classesRoot = sharedLib)) + } + + if (module != null) { + ModuleRootModificationUtil.updateModel(module) { model -> + model.sdk = sdk + } + } + + return createFileAndSyncDependencies(mainScriptFile) + } + + private val oldScripClasspath: String? = System.getProperty("kotlin.script.classpath") + + private var settings: Element? = null + + override fun setUp() { + super.setUp() + + settings = KotlinScriptingSettings.getInstance(project).state + + ScriptDefinitionsManager.getInstance(project).getAllDefinitions().forEach { + KotlinScriptingSettings.getInstance(project).setEnabled(it, false) + } + + setUpTestProject() + } + + open fun setUpTestProject() { + + } + + override fun tearDown() { + System.setProperty("kotlin.script.classpath", oldScripClasspath ?: "") + + settings?.let { + KotlinScriptingSettings.getInstance(project).loadState(it) + } + + super.tearDown() + } + + override fun getTestProjectJdk(): Sdk { + return PluginTestCaseBase.mockJdk() + } + + private fun createTestModuleByName(name: String): Module { + val newModuleDir = runWriteAction { VfsUtil.createDirectoryIfMissing(project.baseDir, name) } + val newModule = createModuleAt(name, project, JavaModuleType.getModuleType(), VfsUtil.virtualToIoFile(newModuleDir).toPath()) + + PsiTestUtil.addSourceContentToRoots(newModule, newModuleDir) + return newModule + } + + private fun createTestModuleFromDir(dir: File): Module { + return createTestModuleByName(dir.name).apply { + PlatformTestCase.copyDirContentsTo(LocalFileSystem.getInstance().findFileByIoFile(dir)!!, moduleFile!!.parent) + } + } + + private fun createScriptEnvironment(scriptFile: File): Environment { + val defaultEnvironment = defaultEnvironment(scriptFile.parent + File.separator) + val env = mutableMapOf() + scriptFile.forEachLine { line -> + + fun iterateKeysInLine(prefix: String) { + if (line.contains(prefix)) { + line.trim().substringAfter(prefix).split(";").forEach { entry -> + val (key, values) = entry.splitOrEmpty(":").map { it.trim() } + assert(key in validKeys) { "Unexpected key: $key" } + env[key] = values.split(",").map { + val str = it.trim() + defaultEnvironment[str] ?: str + } + } + } + } + + iterateKeysInLine(useDefaultTemplate) + iterateKeysInLine(templatesSettings) + + switches.forEach { + if (it in line) { + env[it] = true + } + } + } + + if (env[useDefaultTemplate] != true && env["template-classes-names"] == null) { + env["template-classes-names"] = listOf("custom.scriptDefinition.Template") + } + + val jdkKind = when ((env["javaHome"] as? List)?.singleOrNull()) { + "9" -> TestJdkKind.FULL_JDK_9 + else -> TestJdkKind.MOCK_JDK + } + runWriteAction { + val jdk = PluginTestCaseBase.addJdk(testRootDisposable) { + PluginTestCaseBase.jdk(jdkKind) + } + env["javaHome"] = File(jdk.homePath) + } + + env.putAll(defaultEnvironment) + return env + } + + private fun defaultEnvironment(path: String): Map { + val templateOutDir = File("${path}template").takeIf { it.isDirectory }?.let { + compileLibToDir(it, *scriptClasspath()) + } ?: File("idea/testData/script/definition/defaultTemplate").takeIf { it.isDirectory }?.let { + compileLibToDir(it, *scriptClasspath()) + } + + if (templateOutDir != null) { + System.setProperty("kotlin.script.classpath", templateOutDir.path) + } + + val libSrcDir = File("${path}lib").takeIf { it.isDirectory } + + val libClasses = libSrcDir?.let { compileLibToDir(it) } + + var moduleSrcDir = File("${path}depModule").takeIf { it.isDirectory } + val moduleClasses = moduleSrcDir?.let { compileLibToDir(it) } + if (moduleSrcDir != null) { + val depModule = createTestModuleFromDir(moduleSrcDir) + moduleSrcDir = File(depModule.getModuleDir()) + } + + return mapOf( + "runtime-classes" to ForTestCompileRuntime.runtimeJarForTests(), + "runtime-source" to File("libraries/stdlib/src"), + "lib-classes" to libClasses, + "lib-source" to libSrcDir, + "module-classes" to moduleClasses, + "module-source" to moduleSrcDir, + "template-classes" to templateOutDir + ) + } + + private fun scriptClasspath(): Array { + return with(PathUtil.kotlinPathsForDistDirectory) { + arrayOf( + File(libPath, KOTLIN_JAVA_SCRIPT_RUNTIME_JAR).path, + File(libPath, KOTLIN_SCRIPTING_COMMON_JAR).path, + File(libPath, KOTLIN_SCRIPTING_JVM_JAR).path + ) + } + } + + protected fun createFileAndSyncDependencies(scriptFile: File): VirtualFile { + var script: VirtualFile? = null + if (module != null) { + script = module.moduleFile?.parent?.findChild(scriptFile.name) + } + + if (script == null) { + val target = File(project.basePath, scriptFile.name) + scriptFile.copyTo(target) + script = VfsUtil.findFileByIoFile(target, true) + } + + if (script == null) error("Test file with script couldn't be found in test project") + + configureByExistingFile(script) + loadScriptConfigurationSynchronously(script) + return script!! + } + + protected open fun loadScriptConfigurationSynchronously(script: VirtualFile) { + updateScriptDependenciesSynchronously(myFile) + + // This is needed because updateScriptDependencies invalidates psiFile that was stored in myFile field + VfsUtil.markDirtyAndRefresh(false, true, true, project.baseDir) + myFile = psiManager.findFile(script) + + checkHighlighting() + } + + protected fun checkHighlighting(file: KtFile = myFile as KtFile) { + val reports = IdeScriptReportSink.getReports(file) + val isFatalErrorPresent = reports.any { it.severity == ScriptDiagnostic.Severity.FATAL } + assert(isFatalErrorPresent || KotlinHighlightingUtil.shouldHighlight(file)) { + "Highlighting is switched off for ${file.virtualFile.path}\n" + + "reports=$reports\n" + + "scriptDefinition=${file.findScriptDefinition()}" + } + } + + private fun compileLibToDir(srcDir: File, vararg classpath: String): File { + //TODO: tmpDir would be enough, but there is tricky fail under AS otherwise + val outDir = KotlinTestUtils.tmpDirForReusableFolder("${getTestName(false)}${srcDir.name}Out") + + val kotlinSourceFiles = FileUtil.findFilesByMask(Pattern.compile(".+\\.kt$"), srcDir) + if (kotlinSourceFiles.isNotEmpty()) { + MockLibraryUtil.compileKotlin(srcDir.path, outDir, extraClasspath = *classpath) + } + + val javaSourceFiles = FileUtil.findFilesByMask(Pattern.compile(".+\\.java$"), srcDir) + if (javaSourceFiles.isNotEmpty()) { + KotlinTestUtils.compileJavaFiles( + javaSourceFiles, + listOf("-cp", StringUtil.join(listOf(*classpath, outDir), File.pathSeparator), "-d", outDir.path) + ) + } + return outDir + } + + private fun registerScriptTemplateProvider(environment: Environment) { + val provider = if (environment[useDefaultTemplate] == true) { + FromTextTemplateProvider(environment) + } else { + CustomScriptTemplateProvider(environment) + } + + addExtensionPointInTest( + ScriptDefinitionContributor.EP_NAME, + project, + provider, + testRootDisposable + ) + + ScriptDefinitionsManager.getInstance(project).reloadScriptDefinitions() + + UIUtil.dispatchAllInvocationEvents() + } +}