203: Fix compilation for 203

This commit is contained in:
Florian Kistner
2020-07-06 16:09:26 +02:00
parent 4c4af9971e
commit 346df07adc
23 changed files with 5389 additions and 0 deletions
@@ -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();
}
}
@@ -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<T extends StubElement<?>> extends StubBasedPsiElementBase<T>
implements KtElement, StubBasedPsiElement<T> {
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 <D> void acceptChildren(@NotNull KtVisitor<Void, D> visitor, D data) {
PsiElement child = getFirstChild();
while (child != null) {
if (child instanceof KtElement) {
((KtElement) child).accept(visitor, data);
}
child = child.getNextSibling();
}
}
@Override
public <R, D> R accept(@NotNull KtVisitor<R, D> 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 <PsiT extends KtElementImplStub<?>, StubT extends StubElement<?>> List<PsiT> getStubOrPsiChildrenAsList(
@NotNull KtStubElementType<StubT, PsiT> 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();
}
}
@@ -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<KotlinAnnotationUseSiteTargetStub, KtAnnotationUseSiteTarget>(
debugName, KtAnnotationUseSiteTarget::class.java, KotlinAnnotationUseSiteTargetStub::class.java
) {
override fun createStub(psi: KtAnnotationUseSiteTarget, parentStub: StubElement<out PsiElement>): 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<PsiElement>): KotlinAnnotationUseSiteTargetStub {
val useSiteTarget = dataStream.readName()
return KotlinAnnotationUseSiteTargetStubImpl(parentStub, useSiteTarget!!)
}
}
@@ -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<KotlinImportAliasStub, KtImportAlias>(debugName, KtImportAlias::class.java, KotlinImportAliasStub::class.java) {
override fun createStub(psi: KtImportAlias, parentStub: StubElement<out PsiElement>?): 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<PsiElement>?): KotlinImportAliasStub {
val name = dataStream.readName()
return KotlinImportAliasStubImpl(parentStub, name)
}
}
@@ -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<T extends KtModifierList> extends KtStubElementType<KotlinModifierListStub, T> {
public KtModifierListElementType(@NotNull @NonNls String debugName, @NotNull Class<T> 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);
}
}
@@ -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<T extends KtElementImplStub<? extends StubElement<?>>> extends
KtStubElementType<KotlinPlaceHolderStub<T>, T> {
public KtPlaceHolderStubElementType(@NotNull @NonNls String debugName, @NotNull Class<T> psiClass) {
super(debugName, psiClass, KotlinPlaceHolderStub.class);
}
@Override
public KotlinPlaceHolderStub<T> createStub(@NotNull T psi, StubElement<?> parentStub) {
return new KotlinPlaceHolderStubImpl<>(parentStub, this);
}
@Override
public void serialize(@NotNull KotlinPlaceHolderStub<T> stub, @NotNull StubOutputStream dataStream) throws IOException {
//do nothing
}
@NotNull
@Override
public KotlinPlaceHolderStub<T> deserialize(@NotNull StubInputStream dataStream, StubElement parentStub) throws IOException {
return new KotlinPlaceHolderStubImpl<>(parentStub, this);
}
}
@@ -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<KotlinScriptStub, KtScript>(
debugName, KtScript::class.java, KotlinScriptStub::class.java
) {
override fun createStub(psi: KtScript, parentStub: StubElement<out PsiElement>): 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<PsiElement>): KotlinScriptStub {
val fqName = dataStream.readName()
return KotlinScriptStubImpl(parentStub, fqName)
}
override fun indexStub(stub: KotlinScriptStub, sink: IndexSink) {
StubIndexService.getInstance().indexScript(stub, sink)
}
}
@@ -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<StubT extends StubElement<?>, PsiT extends KtElementImplStub<?>> extends IStubElementType<StubT, PsiT> {
@NotNull
private final Constructor<PsiT> byNodeConstructor;
@NotNull
private final Constructor<PsiT> byStubConstructor;
@NotNull
private final PsiT[] emptyArray;
@NotNull
private final ArrayFactory<PsiT> arrayFactory;
@SuppressWarnings("unchecked")
public KtStubElementType(@NotNull @NonNls String debugName, @NotNull Class<PsiT> 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<PsiT> getArrayFactory() {
return arrayFactory;
}
}
@@ -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<T : KtValueArgument>(debugName: String, psiClass: Class<T>) :
KtStubElementType<KotlinValueArgumentStub<T>, T>(debugName, psiClass, KotlinValueArgumentStub::class.java) {
override fun createStub(psi: T, parentStub: StubElement<out PsiElement>?): KotlinValueArgumentStub<T> {
return KotlinValueArgumentStubImpl(parentStub, this, psi.isSpread)
}
override fun serialize(stub: KotlinValueArgumentStub<T>, dataStream: StubOutputStream) {
dataStream.writeBoolean(stub.isSpread())
}
override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<PsiElement>?): KotlinValueArgumentStub<T> {
val isSpread = dataStream.readBoolean()
return KotlinValueArgumentStubImpl(parentStub, this, isSpread)
}
}
@@ -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<out PsiElement>?,
private val name: StringRef?
) : KotlinStubBaseImpl<KtImportAlias>(parent, KtStubElementTypes.IMPORT_ALIAS), KotlinImportAliasStub {
override fun getName(): String? = StringRef.toString(name)
}
@@ -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<Throwable> testRunnable) throws Throwable {
KotlinTestUtils.runTestWithThrowable(this, () -> super.runTestRunnable(testRunnable));
}
}
@@ -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<Throwable> testRunnable) throws Throwable {
KotlinTestUtils.runTestWithThrowable(this, () -> super.runTestRunnable(testRunnable));
}
}
@@ -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<Path> 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<Throwable> testRunnable) throws Throwable {
KotlinTestUtils.runTestWithThrowable(this, () -> super.runTestRunnable(testRunnable));
}
protected boolean isFirPlugin() {
return false;
}
}
@@ -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<String, String>?) {
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<String> = 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<String, PsiDirectory> {
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<NewKotlinFileHook> =
ExtensionPointName.create<NewKotlinFileHook>("org.jetbrains.kotlin.newFileHook")
}
abstract fun postProcess(createdElement: KtFile, module: Module)
}
@@ -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()
}
}
@@ -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<Module>
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<Throwable>) {
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<Module>,
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")
}
}
@@ -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<Project>(CommonDataKeys.PROJECT, project)
context.put<Editor>(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"))
}
}
@@ -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<KtClassOrObject> =
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<String>()
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<PsiTypeParameter>.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<PsiEnumConstant>()
.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<String>) {
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) }
}
}
@@ -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<KtNamedFunction>() {
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<Throwable>) {
runTest { super.runTestRunnable(testRunnable) }
}
}
@@ -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(<caret>) {}")
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(<caret>) {}")
perform("editingStarted: 'foo()'") { myFixture.type("p: DoubleArra") }
perform("nextSignature: 'foo(p: DoubleArra)'", "nextSignature: 'foo(p: DoubleArray)'") { myFixture.completeBasic() }
}
fun testChangeOutsideSignature() {
setup("fun foo(<caret>) {}")
perform("editingStarted: 'foo()'") { myFixture.type("p: A") }
perform("reset") {
insertString(editor.document.textLength, "\nval")
}
}
fun testEditOtherSignature() {
setup("fun foo(<caret>) {}\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(<caret>) {}")
perform("editingStarted: 'foo()'") { myFixture.type("p: A") }
perform("reset") {
setup("")
myFixture.type(" ")
}
}
fun testAddImport() {
setup("fun foo(<caret>) {}")
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(<caret>) {}
""".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<String>)'") {
myFixture.type("<String>")
commitAll()
}
perform("nextSignature: 'foo(p: ArrayList<String>, p2: Any)'") {
myFixture.type(", p2: Any")
commitAll()
}
}
fun testAddImportWithBlankLinesRemoval() {
setup(
"""
import foo.bar
fun foo(<caret>) {}
""".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<String>)'") {
myFixture.type("<String>")
commitAll()
}
perform("nextSignature: 'foo(p: ArrayList<String>, p2: Any)'") {
myFixture.type(", p2: Any")
commitAll()
}
}
fun testReorderParameters() {
setup("fun foo(p1: String, p2: Any, p3<caret>: 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(<caret>) {}")
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()<caret>
}
""".trimIndent()
)
perform("editingStarted: 'foo()'") { myFixture.type(": String") }
perform("nextSignature: 'foo(): String'") { commitAll() }
}
fun testNewLocal() {
setup(
"""
fun foo() {
<caret>
print(a)
}
""".trimIndent()
)
perform {
myFixture.type("val a")
commitAll()
myFixture.type("bcd")
commitAll()
}
}
fun testNewFunction() {
setup(
"""
interface I {
<caret>
}
""".trimIndent()
)
perform {
myFixture.type("fun foo_bar123(_p1: Int)")
commitAll()
}
}
fun testNewProperty() {
setup(
"""
interface I {
<caret>
}
""".trimIndent()
)
perform {
myFixture.type("val prop: I")
commitAll()
myFixture.type("nt")
commitAll()
}
}
fun testNewLocalWithNewUsage() {
setup(
"""
fun foo() {
<caret>
}
""".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<KtProperty>()!!
myFixture.editor.caretModel.moveToOffset(variable.nameIdentifier!!.endOffset)
myFixture.type("bcd")
commitAll()
}
}
fun testNewLocalBeforeExpression() {
setup(
"""
fun foo(p: Int) {
<caret>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 {
<caret>
}
""".trimIndent()
)
perform {
myFixture.type("constructor(p1: Int)")
commitAll()
}
perform {
myFixture.type("(, p2: String)")
commitAll()
}
}
fun testRenameComponentVar() {
setup(
"""
fun f() {
val (<caret>a, b) = f()
}
""".trimIndent()
)
perform("editingStarted: 'a'", "nextSignature: 'newa'") {
myFixture.type("new")
commitAll()
}
}
override fun runTestRunnable(testRunnable: ThrowableRunnable<Throwable>) {
runTest { super.runTestRunnable(testRunnable) }
}
private fun addImport(fqName: String) {
executeCommand {
runWriteAction {
(file as KtFile).importList!!.add(KtPsiFactory(project).createImportDirective(ImportPath.fromString(fqName)))
}
}
}
}
@@ -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<TestMetadata>()?.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<String, Any?>()
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<String>)?.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<String, File?> {
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<String> {
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()
}
}