KT-1426 Implement "Optimize Imports" in plugin - untested draft version

This commit is contained in:
Nikolay Krasko
2012-03-15 21:22:03 +04:00
parent f93ef619f6
commit 76cc231cb2
17 changed files with 308 additions and 131 deletions
@@ -26,6 +26,7 @@ import org.jetbrains.jet.lang.psi.JetPsiFactory;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
import java.util.Collection;
@@ -36,7 +37,7 @@ import java.util.Collections;
*/
public class JavaBridgeConfiguration implements ModuleConfiguration {
public static final String[] DEFAULT_JAVA_IMPORTS = new String[] { "java.lang.*" };
public static final ImportPath[] DEFAULT_JAVA_IMPORTS = new ImportPath[] { new ImportPath("java.lang.*") };
public static ModuleConfiguration createJavaBridgeConfiguration(@NotNull Project project, @NotNull BindingTrace trace, ModuleConfiguration delegateConfiguration) {
return new JavaBridgeConfiguration(project, trace, delegateConfiguration);
@@ -54,8 +55,8 @@ public class JavaBridgeConfiguration implements ModuleConfiguration {
@Override
public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection<JetImportDirective> directives) {
for (String importFQN : DEFAULT_JAVA_IMPORTS) {
directives.add(JetPsiFactory.createImportDirective(project, importFQN));
for (ImportPath importPath : DEFAULT_JAVA_IMPORTS) {
directives.add(JetPsiFactory.createImportDirective(project, importPath));
}
delegateConfiguration.addDefaultImports(rootScope, directives);
}
@@ -22,6 +22,7 @@ import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor;
import org.jetbrains.jet.lang.psi.JetImportDirective;
import org.jetbrains.jet.lang.psi.JetPsiFactory;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
import java.util.Collection;
@@ -30,7 +31,8 @@ import java.util.Collection;
* @author svtk
*/
public class DefaultModuleConfiguration implements ModuleConfiguration {
public static final String[] DEFAULT_JET_IMPORTS = new String[] { "kotlin.*", "kotlin.io.*" };
public static final ImportPath[] DEFAULT_JET_IMPORTS = new ImportPath[] {
new ImportPath("kotlin.*"), new ImportPath("kotlin.io.*") };
private Project project;
@@ -44,7 +46,7 @@ public class DefaultModuleConfiguration implements ModuleConfiguration {
@Override
public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection<JetImportDirective> directives) {
for (String defaultJetImport : DEFAULT_JET_IMPORTS) {
for (ImportPath defaultJetImport : DEFAULT_JET_IMPORTS) {
directives.add(JetPsiFactory.createImportDirective(project, defaultJetImport));
}
}
@@ -52,5 +54,4 @@ public class DefaultModuleConfiguration implements ModuleConfiguration {
@Override
public void extendNamespaceScope(@NotNull BindingTrace trace, @NotNull NamespaceDescriptor namespaceDescriptor, @NotNull WritableScope namespaceMemberScope) {
}
}
@@ -24,7 +24,7 @@ import com.intellij.psi.PsiFileFactory;
import com.intellij.util.LocalTimeCounter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lexer.JetKeywordToken;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetFileType;
@@ -140,21 +140,35 @@ public class JetPsiFactory {
return function.getValueParameters().get(0);
}
// public static JetNamespace createNamespace(Project project, String text) {
// JetFile file = createFile(project, text);
// return file.getRootNamespace();
// }
public static JetImportDirective createImportDirective(Project project, @NotNull String classPath) {
if (classPath.isEmpty()) {
throw new IllegalArgumentException("import path must not be empty");
}
JetFile namespace = createFile(project, "import " + classPath);
return namespace.getImportDirectives().iterator().next();
@NotNull
public static JetImportDirective createImportDirective(Project project, @NotNull String path) {
return createImportDirective(project, new ImportPath(path));
}
public static JetImportDirective createImportDirective(Project project, @NotNull FqName fqName) {
return createImportDirective(project, fqName.getFqName());
@NotNull
public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath) {
return createImportDirective(project, importPath, null);
}
@NotNull
public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath, @Nullable String aliasName) {
if (importPath.fqnPart().isRoot()) {
throw new IllegalArgumentException("import path must not be empty");
}
StringBuilder importDirectiveBuilder = new StringBuilder("import ");
importDirectiveBuilder.append(importPath.getPathStr());
if (aliasName != null) {
if (aliasName.isEmpty()) {
throw new IllegalArgumentException("Alias must not be empty");
}
importDirectiveBuilder.append(" as ").append(aliasName);
}
JetFile namespace = createFile(project, importDirectiveBuilder.toString());
return namespace.getImportDirectives().iterator().next();
}
public static PsiElement createPrimaryConstructor(Project project) {
@@ -23,8 +23,8 @@ import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.util.QualifiedNamesUtil;
import java.util.Collection;
import java.util.HashSet;
@@ -194,14 +194,14 @@ public class JetPsiUtil {
}
@Nullable @JetElement.IfNotParsed
public static String getImportPath(JetImportDirective importDirective) {
public static ImportPath getImportPath(JetImportDirective importDirective) {
final JetExpression importedReference = importDirective.getImportedReference();
if (importedReference == null) {
return null;
}
final String text = importedReference.getText();
return text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : "");
return new ImportPath(text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : ""));
}
@NotNull
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve;
import org.jetbrains.annotations.NotNull;
/**
* @author Nikolay Krasko
*/
public final class ImportPath {
final @NotNull FqName fqName;
final boolean isAllUnder;
public ImportPath(@NotNull FqName fqName, boolean isAllUnder) {
this.fqName = fqName;
this.isAllUnder = isAllUnder;
}
public ImportPath(@NotNull String pathStr) {
if (pathStr.endsWith(".*")) {
this.isAllUnder = true;
this.fqName = new FqName(pathStr.substring(0, pathStr.length() - 2));
}
else {
this.isAllUnder = false;
this.fqName = new FqName(pathStr);
}
}
public String getPathStr() {
return fqName.getFqName() + (isAllUnder ? ".*" : "");
}
@NotNull
public FqName fqnPart() {
return fqName;
}
public boolean isAllUnder() {
return isAllUnder;
}
@Override
public int hashCode() {
return 31 * fqName.hashCode() + (isAllUnder ? 1 : 0);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof ImportPath)) return false;
ImportPath other = (ImportPath) obj;
return fqName.equals(other.fqName) && (isAllUnder == other.isAllUnder);
}
}
@@ -19,14 +19,18 @@ package org.jetbrains.jet.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
/**
* Common methods for working with qualified names strings.
* Common methods for working with qualified names.
*
* @author Nikolay Krasko
*/
public final class QualifiedNamesUtil {
private QualifiedNamesUtil() {
}
public static boolean isSubpackageOf(@NotNull final FqName subpackageName, @NotNull FqName packageName) {
return subpackageName.equals(packageName) ||
(subpackageName.getFqName().startsWith(packageName.getFqName()) && subpackageName.getFqName().charAt(packageName.getFqName().length()) == '.');
@@ -45,6 +49,10 @@ public final class QualifiedNamesUtil {
return fqn.indexOf('.') < 0;
}
public static boolean isOneSegmentFQN(@NotNull FqName fqn) {
return isOneSegmentFQN(fqn.getFqName());
}
@NotNull
public static String fqnToShortName(@NotNull FqName fqn) {
return getLastSegment(fqn);
@@ -62,18 +70,18 @@ public final class QualifiedNamesUtil {
}
@NotNull
public static String withoutLastSegment(@NotNull String fqn) {
final int lastDotIndex = fqn.lastIndexOf('.');
if (lastDotIndex > 0) {
return fqn.substring(0, lastDotIndex);
}
return "";
public static FqName withoutLastSegment(@NotNull FqName fqName) {
return fqName.parent();
}
@NotNull
public static FqName withoutLastSegment(@NotNull FqName fqName) {
return fqName.parent();
public static FqName withoutFirstSegment(@NotNull FqName fqName) {
if (fqName.isRoot() || fqName.parent().isRoot()) {
return FqName.ROOT;
}
String fqNameStr = fqName.getFqName();
return new FqName(fqNameStr.substring(fqNameStr.indexOf('.'), fqNameStr.length()));
}
@NotNull
@@ -121,18 +129,19 @@ public final class QualifiedNamesUtil {
return null;
}
/**
* Check that given fqn could be imported with import.
*
* @param importPath path from the import. Could contain .* part
* @param fqn or another import directive
*/
public static boolean isImported(@NotNull String importPath, @NotNull String fqn) {
if (importPath.endsWith("*")) {
// TODO: import path is not valid FQN
return withoutLastSegment(importPath).equals(withoutLastSegment(fqn));
public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) {
if (alreadyImported.isAllUnder()) {
return alreadyImported.fqnPart().equals(fqName.parent());
}
return importPath.equals(fqn);
return alreadyImported.fqnPart().equals(fqName);
}
public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull ImportPath newImport) {
if (newImport.isAllUnder()) {
return alreadyImported.equals(newImport);
}
return isImported(alreadyImported, newImport.fqnPart());
}
}
@@ -33,7 +33,8 @@ import com.intellij.psi.PsiFile;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.plugin.quickfix.ImportClassHelper;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
import javax.swing.*;
import java.util.List;
@@ -49,7 +50,7 @@ public class JetAddImportAction implements QuestionAction {
private final Project myProject;
private final Editor myEditor;
private final PsiElement myElement;
private final List<String> possibleImports;
private final List<FqName> possibleImports;
/**
* @param project Project where action takes place.
@@ -61,7 +62,7 @@ public class JetAddImportAction implements QuestionAction {
@NotNull Project project,
@NotNull Editor editor,
@NotNull PsiElement element,
@NotNull Iterable<String> imports
@NotNull Iterable<FqName> imports
) {
myProject = project;
myEditor = editor;
@@ -90,14 +91,14 @@ public class JetAddImportAction implements QuestionAction {
}
protected BaseListPopupStep getImportSelectionPopup() {
return new BaseListPopupStep<String>(QuickFixBundle.message("class.to.import.chooser.title"), possibleImports) {
return new BaseListPopupStep<FqName>(QuickFixBundle.message("class.to.import.chooser.title"), possibleImports) {
@Override
public boolean isAutoSelectionEnabled() {
return false;
}
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
public PopupStep onChosen(FqName selectedValue, boolean finalChoice) {
if (selectedValue == null) {
return FINAL_CHOICE;
}
@@ -107,7 +108,7 @@ public class JetAddImportAction implements QuestionAction {
return FINAL_CHOICE;
}
List<String> toExclude = AddImportAction.getAllExcludableStrings(selectedValue);
List<String> toExclude = AddImportAction.getAllExcludableStrings(selectedValue.getFqName());
return new BaseListPopupStep<String>(null, toExclude) {
@NotNull
@@ -128,24 +129,25 @@ public class JetAddImportAction implements QuestionAction {
}
@Override
public boolean hasSubstep(String selectedValue) {
public boolean hasSubstep(FqName selectedValue) {
return true;
}
@NotNull
@Override
public String getTextFor(String value) {
return value;
public String getTextFor(FqName value) {
return value.getFqName();
}
@Override
public Icon getIconFor(String aValue) {
public Icon getIconFor(FqName aValue) {
// TODO: change icon
return PlatformIcons.CLASS_ICON;
}
};
}
protected static void addImport(final PsiElement element, final Project project, final String selectedImport) {
protected static void addImport(final PsiElement element, final Project project, final FqName selectedImport) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@@ -154,13 +156,10 @@ public class JetAddImportAction implements QuestionAction {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
// TODO: See {@link com.intellij.codeInsight.daemon.impl.actions.AddImportAction#_addImport} for more ideas.
// TODO: Optimize imports
PsiFile file = element.getContainingFile();
if (!(file instanceof JetFile)) return;
ImportClassHelper.addImportDirective(
selectedImport,
(JetFile)file
ImportInsertHelper.addImportDirective(selectedImport,
(JetFile) file
);
}
});
@@ -36,10 +36,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.asJava.JavaElementFinder;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingTraceContext;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportsResolver;
import org.jetbrains.jet.lang.resolve.*;
import org.jetbrains.jet.lang.resolve.scopes.JetScope;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils;
@@ -177,7 +174,7 @@ public class JetShortNamesCache extends PsiShortNamesCache {
for (PsiMethod method : topLevelFunctionPrototypes) {
FqName functionFQN = JetFromJavaDescriptorHelper.getJetTopLevelDeclarationFQN(method);
if (functionFQN != null) {
JetImportDirective importDirective = JetPsiFactory.createImportDirective(project, functionFQN);
JetImportDirective importDirective = JetPsiFactory.createImportDirective(project, new ImportPath(functionFQN, false));
Collection<? extends DeclarationDescriptor> declarationDescriptors = ImportsResolver.analyseImportReference(importDirective, jetScope, new BindingTraceContext());
for (DeclarationDescriptor declarationDescriptor : declarationDescriptors) {
if (declarationDescriptor instanceof FunctionDescriptor) {
@@ -34,7 +34,7 @@ import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.JetStandardLibrary;
import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade;
import org.jetbrains.jet.plugin.quickfix.ImportClassHelper;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -93,7 +93,7 @@ public abstract class OverrideImplementMethodsHandler implements LanguageCodeIns
bodyBuilder.append("val ");
}
bodyBuilder.append(descriptor.getName()).append(":").append(descriptor.getType());
ImportClassHelper.addImportDirectiveIfNeeded(descriptor.getType(), file);
ImportInsertHelper.addImportDirectiveIfNeeded(descriptor.getType(), file);
String initializer = defaultInitializer(descriptor.getType(), JetStandardLibrary.getInstance());
if (initializer != null) {
bodyBuilder.append("=").append(initializer);
@@ -118,14 +118,14 @@ public abstract class OverrideImplementMethodsHandler implements LanguageCodeIns
bodyBuilder.append(":");
bodyBuilder.append(parameterDescriptor.getType().toString());
ImportClassHelper.addImportDirectiveIfNeeded(parameterDescriptor.getType(), file);
ImportInsertHelper.addImportDirectiveIfNeeded(parameterDescriptor.getType(), file);
}
bodyBuilder.append(")");
final JetType returnType = descriptor.getReturnType();
final JetStandardLibrary stdlib = JetStandardLibrary.getInstance();
if (!returnType.equals(stdlib.getTuple0Type())) {
bodyBuilder.append(":").append(returnType.toString());
ImportClassHelper.addImportDirectiveIfNeeded(returnType, file);
ImportInsertHelper.addImportDirectiveIfNeeded(returnType, file);
}
bodyBuilder.append("{").append("throw UnsupportedOperationException()").append("}");
@@ -31,12 +31,11 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetImportDirective;
import org.jetbrains.jet.lang.psi.JetPsiUtil;
import org.jetbrains.jet.lang.psi.JetQualifiedExpression;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.plugin.completion.JetLookupObject;
import org.jetbrains.jet.plugin.quickfix.ImportClassHelper;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
/**
* Performs number of code modification after insertion jet function:
@@ -135,7 +134,7 @@ public class JetFunctionInsertHandler implements InsertHandler<LookupElement> {
@Override
public void run() {
final FqName fqn = DescriptorUtils.getFQName(functionDescriptor).toSafe();
ImportClassHelper.addImportDirective(fqn.getFqName(), file);
ImportInsertHelper.addImportDirective(fqn, file);
}
});
}
@@ -20,7 +20,8 @@ import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.plugin.quickfix.ImportClassHelper;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
/**
* Handler for inserting java class completion.
@@ -34,7 +35,7 @@ public class JetJavaClassInsertHandler implements InsertHandler<JavaPsiClassRefe
public void handleInsert(final InsertionContext context, final JavaPsiClassReferenceElement item) {
if (context.getFile() instanceof JetFile) {
final JetFile jetFile = (JetFile) context.getFile();
ImportClassHelper.addImportDirective(item.getQualifiedName(), jetFile);
ImportInsertHelper.addImportDirective(new FqName(item.getQualifiedName()), jetFile);
}
// check annotation
@@ -17,19 +17,20 @@
package org.jetbrains.jet.plugin.editor.importOptimizer;
import com.intellij.lang.ImportOptimizer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.java.JvmAbi;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
import org.jetbrains.jet.util.QualifiedNamesUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* @author Nikolay Krasko
@@ -47,17 +48,79 @@ public class JetImportOptimizer implements ImportOptimizer {
@Override
public void run() {
JetFile jetFile = (JetFile) file;
Set<String> usedQualifiedNames = extractUsedQualifiedNames(jetFile);
final JetFile jetFile = (JetFile) file;
final Set<FqName> usedQualifiedNames = extractUsedQualifiedNames(jetFile);
final List<JetImportDirective> sortedDirectives = jetFile.getImportDirectives();
Collections.sort(sortedDirectives, new Comparator<JetImportDirective>() {
@Override
public int compare(JetImportDirective directive1, JetImportDirective directive2) {
ImportPath firstPath = JetPsiUtil.getImportPath(directive1);
ImportPath secondPath = JetPsiUtil.getImportPath(directive2);
if (firstPath == null || secondPath == null) {
return firstPath == null && secondPath == null ? 0 :
firstPath == null ? -1 :
1;
}
// import bla.bla.bla.* should be before import bla.bla.bla.something
if (firstPath.isAllUnder() && !secondPath.isAllUnder() && firstPath.fqnPart().equals(secondPath.fqnPart().parent())) {
return -1;
}
if (!firstPath.isAllUnder() && secondPath.isAllUnder() && secondPath.fqnPart().equals(firstPath.fqnPart().parent())) {
return 1;
}
return firstPath.getPathStr().compareTo(secondPath.getPathStr());
}
});
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
// Remove imports
List<JetImportDirective> imports = jetFile.getImportDirectives();
if (!imports.isEmpty()) {
jetFile.deleteChildRange(imports.get(0), imports.get(imports.size() - 1));
}
// Insert back only necessary imports in correct order
for (JetImportDirective anImport : sortedDirectives) {
ImportPath importPath = JetPsiUtil.getImportPath(anImport);
if (importPath == null) {
continue;
}
if (isUseful(importPath, anImport.getAliasName(), usedQualifiedNames)) {
ImportInsertHelper.addImportDirective(importPath, anImport.getAliasName(), jetFile);
}
}
}
});
}
};
}
public static Set<String> extractUsedQualifiedNames(JetFile jetFile) {
public static boolean isUseful(ImportPath importPath, @Nullable String aliasName, Collection<FqName> usedNames) {
if (aliasName != null) {
// TODO: Add better analysis for aliases
return true;
}
final Set<String> usedQualifiedNames = new HashSet<String>();
for (FqName usedName : usedNames) {
if (QualifiedNamesUtil.isImported(importPath, usedName)) {
return true;
}
}
return false;
}
public static Set<FqName> extractUsedQualifiedNames(JetFile jetFile) {
final Set<FqName> usedQualifiedNames = new HashSet<FqName>();
jetFile.accept(new JetVisitorVoid() {
@Override
public void visitElement(PsiElement element) {
@@ -83,7 +146,7 @@ public class JetImportOptimizer implements ImportOptimizer {
}
for (PsiElement psiReference : references) {
String fqName = getElementFQName(psiReference);
FqName fqName = getElementFQName(psiReference);
if (fqName != null) {
usedQualifiedNames.add(fqName);
}
@@ -101,7 +164,7 @@ public class JetImportOptimizer implements ImportOptimizer {
@Nullable
public static String getElementFQName(PsiElement element) {
public static FqName getElementFQName(PsiElement element) {
if (element instanceof JetClassOrObject) {
return JetPsiUtil.getFQName((JetClassOrObject) element);
}
@@ -109,7 +172,10 @@ public class JetImportOptimizer implements ImportOptimizer {
return JetPsiUtil.getFQName((JetNamedFunction) element);
}
if (element instanceof PsiClass) {
return ((PsiClass) element).getQualifiedName();
String qualifiedName = ((PsiClass) element).getQualifiedName();
if (qualifiedName != null) {
return new FqName(qualifiedName);
}
}
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod) element;
@@ -117,15 +183,14 @@ public class JetImportOptimizer implements ImportOptimizer {
PsiClass containingClass = method.getContainingClass();
if (containingClass != null) {
String classFQN = containingClass.getQualifiedName();
if (classFQN != null) {
if (QualifiedNamesUtil.fqnToShortName(classFQN).equals(JvmAbi.PACKAGE_CLASS)) {
String classParentFQN = QualifiedNamesUtil.withoutLastSegment(classFQN);
return QualifiedNamesUtil.combine(classParentFQN, method.getName());
String classFQNStr = containingClass.getQualifiedName();
if (classFQNStr != null) {
FqName classFQN = new FqName(classFQNStr);
if (classFQN.shortName().equals(JvmAbi.PACKAGE_CLASS)) {
return QualifiedNamesUtil.combine(classFQN.parent(), method.getName());
}
else {
return QualifiedNamesUtil.combine(containingClass.getQualifiedName(), method.getName());
return QualifiedNamesUtil.combine(classFQN, method.getName());
}
}
}
@@ -68,7 +68,7 @@ public class AddReturnTypeFix extends JetIntentionAction<JetNamedDeclaration> {
assert element instanceof JetFunction;
newElement = addFunctionType(project, (JetFunction) element, type);
}
ImportClassHelper.addImportDirectiveIfNeeded(type, (JetFile) file);
ImportInsertHelper.addImportDirectiveIfNeeded(type, (JetFile) file);
element.replace(newElement);
}
@@ -225,13 +225,7 @@ public class ImportClassAndFunFix extends JetHintAction<JetSimpleNameExpression>
@NotNull
private JetAddImportAction createAction(@NotNull Project project, @NotNull Editor editor) {
Collection<String> suggesionStrings = Collections2.transform(suggestions, new Function<FqName, String>() {
@Override
public String apply(FqName fqName) {
return fqName.getFqName();
}
});
return new JetAddImportAction(project, editor, element, suggesionStrings);
return new JetAddImportAction(project, editor, element, suggestions);
}
@Nullable
@@ -18,10 +18,12 @@ package org.jetbrains.jet.plugin.quickfix;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.DefaultModuleConfiguration;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
import org.jetbrains.jet.lang.resolve.java.JavaBridgeConfiguration;
import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver;
@@ -35,7 +37,10 @@ import java.util.List;
/**
* @author svtk
*/
public class ImportClassHelper {
public class ImportInsertHelper {
private ImportInsertHelper() {
}
/**
* Add import directive corresponding to a type to file when it is needed.
*
@@ -51,36 +56,42 @@ public class ImportClassHelper {
if (element != null && element.getContainingFile() == file) { //declaration is in the same file, so no import is needed
return;
}
addImportDirective(JetPluginUtil.computeTypeFullName(type).getFqName(), file);
addImportDirective(JetPluginUtil.computeTypeFullName(type), file);
}
/**
* Add import directive into the PSI tree for the given namespace.
*
* @param importString full name of the import. Can contain .* if necessary.
* @param importFqn full name of the import
* @param file File where directive should be added.
*/
public static void addImportDirective(@NotNull String importString, @NotNull JetFile file) {
public static void addImportDirective(@NotNull FqName importFqn, @NotNull JetFile file) {
addImportDirective(new ImportPath(importFqn, false), null, file);
}
// TODO: hack
if (importString.startsWith(JavaDescriptorResolver.JAVA_ROOT + ".")) {
importString = importString.substring((JavaDescriptorResolver.JAVA_ROOT + ".").length());
public static void addImportDirective(@NotNull ImportPath importPath, @Nullable String aliasName, @NotNull JetFile file) {
if (QualifiedNamesUtil.getFirstSegment(importPath.fqnPart().getFqName()).equals(JavaDescriptorResolver.JAVA_ROOT)) {
FqName withoutJavaRoot = QualifiedNamesUtil.withoutFirstSegment(importPath.fqnPart());
importPath = new ImportPath(withoutJavaRoot, importPath.isAllUnder());
}
if (isImportedByDefault(importString, JetPsiUtil.getFQName(file))) {
if (isImportedByDefault(importPath, aliasName, JetPsiUtil.getFQName(file))) {
return;
}
List<JetImportDirective> importDirectives = file.getImportDirectives();
JetImportDirective newDirective = JetPsiFactory.createImportDirective(file.getProject(), importPath, aliasName);
JetImportDirective newDirective = JetPsiFactory.createImportDirective(file.getProject(), importString);
List<JetImportDirective> importDirectives = file.getImportDirectives();
if (!importDirectives.isEmpty()) {
// Check if import is already present
for (JetImportDirective directive : importDirectives) {
String importPath = JetPsiUtil.getImportPath(directive);
if (importPath != null && QualifiedNamesUtil.isImported(importPath, importString)) {
ImportPath existentImportPath = JetPsiUtil.getImportPath(directive);
if (existentImportPath != null &&
directive.getAliasName() == null &&
QualifiedNamesUtil.isImported(existentImportPath, importPath)) {
return;
}
}
@@ -101,25 +112,40 @@ public class ImportClassHelper {
}
}
// Check that import is useless
private static boolean isImportedByDefault(@NotNull String importString, @NotNull FqName filePackageFqn) {
if (QualifiedNamesUtil.isOneSegmentFQN(importString) ||
filePackageFqn.getFqName().equals(QualifiedNamesUtil.withoutLastSegment(importString))) {
/**
* Check that import is useless.
*/
private static boolean isImportedByDefault(@NotNull ImportPath importPath, String aliasName, @NotNull FqName filePackageFqn) {
if (importPath.fqnPart().isRoot()) {
return true;
}
for (String defaultJetImport : DefaultModuleConfiguration.DEFAULT_JET_IMPORTS) {
if (QualifiedNamesUtil.isImported(defaultJetImport, importString)) {
if (aliasName != null) {
return false;
}
// Single element import without .* and alias is useless
if (!importPath.isAllUnder() && QualifiedNamesUtil.isOneSegmentFQN(importPath.fqnPart())) {
return true;
}
// There's no need to import a declaration from the package of current file
if (!importPath.isAllUnder() && filePackageFqn.equals(importPath.fqnPart().parent())) {
return true;
}
for (ImportPath defaultJetImport : DefaultModuleConfiguration.DEFAULT_JET_IMPORTS) {
if (QualifiedNamesUtil.isImported(defaultJetImport, importPath)) {
return true;
}
}
for (String defaultJavaImport : JavaBridgeConfiguration.DEFAULT_JAVA_IMPORTS) {
if (QualifiedNamesUtil.isImported(defaultJavaImport + ".*", importString)) {
for (ImportPath defaultJavaImport : JavaBridgeConfiguration.DEFAULT_JAVA_IMPORTS) {
if (QualifiedNamesUtil.isImported(defaultJavaImport, importPath)) {
return true;
}
}
return false;
}
}
@@ -119,7 +119,7 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction<JetProperty>
}
}
if (needImport) {
ImportClassHelper.addImportDirectiveIfNeeded(type, (JetFile)file);
ImportInsertHelper.addImportDirectiveIfNeeded(type, (JetFile) file);
}
element.replace(newElement);
}
@@ -19,6 +19,7 @@ package org.jetbrains.jet.plugin.quickfix;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.plugin.PluginTestCaseBase;
import java.io.IOException;
@@ -28,19 +29,19 @@ import java.io.IOException;
*/
public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase {
public void testDoNotImportIfGeneralExist() {
testImportInFile("jettesting.data.testFunction");
doFileTest("jettesting.data.testFunction");
}
public void testDoNotImportIfGeneralSpaceExist() {
testImportInFile("jettesting.data.testFunction");
doFileTest("jettesting.data.testFunction");
}
public void testNoDefaultImport() {
testImportInFile("kotlin.io.println");
doFileTest("kotlin.io.println");
}
public void testImportBeforeObject() {
testImportInFile("java.util.HashSet");
doFileTest("java.util.HashSet");
}
public void testInsertInEmptyFile() throws IOException {
@@ -48,7 +49,7 @@ public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ImportClassHelper.addImportDirective("java.util.ArrayList", (JetFile) getFile());
ImportInsertHelper.addImportDirective(new FqName("java.util.ArrayList"), (JetFile) getFile());
}
});
@@ -60,19 +61,19 @@ public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ImportClassHelper.addImportDirective("java.util.ArrayList", (JetFile) getFile());
ImportInsertHelper.addImportDirective(new FqName("java.util.ArrayList"), (JetFile) getFile());
}
});
checkResultByText("package some\n\nimport java.util.ArrayList");
}
public void testImportInFile(final String importString) {
public void doFileTest(final String importString) {
configureByFile(getTestName(false) + ".kt");
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ImportClassHelper.addImportDirective(importString, (JetFile) getFile());
ImportInsertHelper.addImportDirective(new FqName(importString), (JetFile) getFile());
}
});
checkResultByFile(getTestName(false) + ".kt.after");