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.BindingTrace;
import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.FqName; import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.scopes.WritableScope; import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
import java.util.Collection; import java.util.Collection;
@@ -36,7 +37,7 @@ import java.util.Collections;
*/ */
public class JavaBridgeConfiguration implements ModuleConfiguration { 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) { public static ModuleConfiguration createJavaBridgeConfiguration(@NotNull Project project, @NotNull BindingTrace trace, ModuleConfiguration delegateConfiguration) {
return new JavaBridgeConfiguration(project, trace, delegateConfiguration); return new JavaBridgeConfiguration(project, trace, delegateConfiguration);
@@ -54,8 +55,8 @@ public class JavaBridgeConfiguration implements ModuleConfiguration {
@Override @Override
public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection<JetImportDirective> directives) { public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection<JetImportDirective> directives) {
for (String importFQN : DEFAULT_JAVA_IMPORTS) { for (ImportPath importPath : DEFAULT_JAVA_IMPORTS) {
directives.add(JetPsiFactory.createImportDirective(project, importFQN)); directives.add(JetPsiFactory.createImportDirective(project, importPath));
} }
delegateConfiguration.addDefaultImports(rootScope, directives); 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.JetImportDirective;
import org.jetbrains.jet.lang.psi.JetPsiFactory; import org.jetbrains.jet.lang.psi.JetPsiFactory;
import org.jetbrains.jet.lang.resolve.BindingTrace; import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lang.resolve.scopes.WritableScope; import org.jetbrains.jet.lang.resolve.scopes.WritableScope;
import java.util.Collection; import java.util.Collection;
@@ -30,7 +31,8 @@ import java.util.Collection;
* @author svtk * @author svtk
*/ */
public class DefaultModuleConfiguration implements ModuleConfiguration { 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; private Project project;
@@ -44,7 +46,7 @@ public class DefaultModuleConfiguration implements ModuleConfiguration {
@Override @Override
public void addDefaultImports(@NotNull WritableScope rootScope, @NotNull Collection<JetImportDirective> directives) { 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)); directives.add(JetPsiFactory.createImportDirective(project, defaultJetImport));
} }
} }
@@ -52,5 +54,4 @@ public class DefaultModuleConfiguration implements ModuleConfiguration {
@Override @Override
public void extendNamespaceScope(@NotNull BindingTrace trace, @NotNull NamespaceDescriptor namespaceDescriptor, @NotNull WritableScope namespaceMemberScope) { 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 com.intellij.util.LocalTimeCounter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; 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.JetKeywordToken;
import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetFileType; import org.jetbrains.jet.plugin.JetFileType;
@@ -140,21 +140,35 @@ public class JetPsiFactory {
return function.getValueParameters().get(0); return function.getValueParameters().get(0);
} }
// public static JetNamespace createNamespace(Project project, String text) { @NotNull
// JetFile file = createFile(project, text); public static JetImportDirective createImportDirective(Project project, @NotNull String path) {
// return file.getRootNamespace(); return createImportDirective(project, new ImportPath(path));
// }
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();
} }
public static JetImportDirective createImportDirective(Project project, @NotNull FqName fqName) { @NotNull
return createImportDirective(project, fqName.getFqName()); 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) { 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName; import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lang.resolve.ImportPath;
import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.util.QualifiedNamesUtil;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@@ -194,14 +194,14 @@ public class JetPsiUtil {
} }
@Nullable @JetElement.IfNotParsed @Nullable @JetElement.IfNotParsed
public static String getImportPath(JetImportDirective importDirective) { public static ImportPath getImportPath(JetImportDirective importDirective) {
final JetExpression importedReference = importDirective.getImportedReference(); final JetExpression importedReference = importDirective.getImportedReference();
if (importedReference == null) { if (importedReference == null) {
return null; return null;
} }
final String text = importedReference.getText(); final String text = importedReference.getText();
return text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : ""); return new ImportPath(text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : ""));
} }
@NotNull @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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName; 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 * @author Nikolay Krasko
*/ */
public final class QualifiedNamesUtil { public final class QualifiedNamesUtil {
private QualifiedNamesUtil() {
}
public static boolean isSubpackageOf(@NotNull final FqName subpackageName, @NotNull FqName packageName) { public static boolean isSubpackageOf(@NotNull final FqName subpackageName, @NotNull FqName packageName) {
return subpackageName.equals(packageName) || return subpackageName.equals(packageName) ||
(subpackageName.getFqName().startsWith(packageName.getFqName()) && subpackageName.getFqName().charAt(packageName.getFqName().length()) == '.'); (subpackageName.getFqName().startsWith(packageName.getFqName()) && subpackageName.getFqName().charAt(packageName.getFqName().length()) == '.');
@@ -45,6 +49,10 @@ public final class QualifiedNamesUtil {
return fqn.indexOf('.') < 0; return fqn.indexOf('.') < 0;
} }
public static boolean isOneSegmentFQN(@NotNull FqName fqn) {
return isOneSegmentFQN(fqn.getFqName());
}
@NotNull @NotNull
public static String fqnToShortName(@NotNull FqName fqn) { public static String fqnToShortName(@NotNull FqName fqn) {
return getLastSegment(fqn); return getLastSegment(fqn);
@@ -62,18 +70,18 @@ public final class QualifiedNamesUtil {
} }
@NotNull @NotNull
public static String withoutLastSegment(@NotNull String fqn) { public static FqName withoutLastSegment(@NotNull FqName fqName) {
final int lastDotIndex = fqn.lastIndexOf('.'); return fqName.parent();
if (lastDotIndex > 0) {
return fqn.substring(0, lastDotIndex);
}
return "";
} }
@NotNull @NotNull
public static FqName withoutLastSegment(@NotNull FqName fqName) { public static FqName withoutFirstSegment(@NotNull FqName fqName) {
return fqName.parent(); if (fqName.isRoot() || fqName.parent().isRoot()) {
return FqName.ROOT;
}
String fqNameStr = fqName.getFqName();
return new FqName(fqNameStr.substring(fqNameStr.indexOf('.'), fqNameStr.length()));
} }
@NotNull @NotNull
@@ -121,18 +129,19 @@ public final class QualifiedNamesUtil {
return null; return null;
} }
/** public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) {
* Check that given fqn could be imported with import. if (alreadyImported.isAllUnder()) {
* return alreadyImported.fqnPart().equals(fqName.parent());
* @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));
} }
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 com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.JetFile; 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 javax.swing.*;
import java.util.List; import java.util.List;
@@ -49,7 +50,7 @@ public class JetAddImportAction implements QuestionAction {
private final Project myProject; private final Project myProject;
private final Editor myEditor; private final Editor myEditor;
private final PsiElement myElement; private final PsiElement myElement;
private final List<String> possibleImports; private final List<FqName> possibleImports;
/** /**
* @param project Project where action takes place. * @param project Project where action takes place.
@@ -61,7 +62,7 @@ public class JetAddImportAction implements QuestionAction {
@NotNull Project project, @NotNull Project project,
@NotNull Editor editor, @NotNull Editor editor,
@NotNull PsiElement element, @NotNull PsiElement element,
@NotNull Iterable<String> imports @NotNull Iterable<FqName> imports
) { ) {
myProject = project; myProject = project;
myEditor = editor; myEditor = editor;
@@ -90,14 +91,14 @@ public class JetAddImportAction implements QuestionAction {
} }
protected BaseListPopupStep getImportSelectionPopup() { 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 @Override
public boolean isAutoSelectionEnabled() { public boolean isAutoSelectionEnabled() {
return false; return false;
} }
@Override @Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) { public PopupStep onChosen(FqName selectedValue, boolean finalChoice) {
if (selectedValue == null) { if (selectedValue == null) {
return FINAL_CHOICE; return FINAL_CHOICE;
} }
@@ -107,7 +108,7 @@ public class JetAddImportAction implements QuestionAction {
return FINAL_CHOICE; return FINAL_CHOICE;
} }
List<String> toExclude = AddImportAction.getAllExcludableStrings(selectedValue); List<String> toExclude = AddImportAction.getAllExcludableStrings(selectedValue.getFqName());
return new BaseListPopupStep<String>(null, toExclude) { return new BaseListPopupStep<String>(null, toExclude) {
@NotNull @NotNull
@@ -128,24 +129,25 @@ public class JetAddImportAction implements QuestionAction {
} }
@Override @Override
public boolean hasSubstep(String selectedValue) { public boolean hasSubstep(FqName selectedValue) {
return true; return true;
} }
@NotNull @NotNull
@Override @Override
public String getTextFor(String value) { public String getTextFor(FqName value) {
return value; return value.getFqName();
} }
@Override @Override
public Icon getIconFor(String aValue) { public Icon getIconFor(FqName aValue) {
// TODO: change icon
return PlatformIcons.CLASS_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(); PsiDocumentManager.getInstance(project).commitAllDocuments();
CommandProcessor.getInstance().executeCommand(project, new Runnable() { CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@@ -154,13 +156,10 @@ public class JetAddImportAction implements QuestionAction {
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override @Override
public void run() { public void run() {
// TODO: See {@link com.intellij.codeInsight.daemon.impl.actions.AddImportAction#_addImport} for more ideas.
// TODO: Optimize imports
PsiFile file = element.getContainingFile(); PsiFile file = element.getContainingFile();
if (!(file instanceof JetFile)) return; if (!(file instanceof JetFile)) return;
ImportClassHelper.addImportDirective( ImportInsertHelper.addImportDirective(selectedImport,
selectedImport, (JetFile) file
(JetFile)file
); );
} }
}); });
@@ -36,10 +36,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.asJava.JavaElementFinder; import org.jetbrains.jet.asJava.JavaElementFinder;
import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.*;
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.scopes.JetScope; import org.jetbrains.jet.lang.resolve.scopes.JetScope;
import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils; import org.jetbrains.jet.lang.types.expressions.ExpressionTypingUtils;
@@ -177,7 +174,7 @@ public class JetShortNamesCache extends PsiShortNamesCache {
for (PsiMethod method : topLevelFunctionPrototypes) { for (PsiMethod method : topLevelFunctionPrototypes) {
FqName functionFQN = JetFromJavaDescriptorHelper.getJetTopLevelDeclarationFQN(method); FqName functionFQN = JetFromJavaDescriptorHelper.getJetTopLevelDeclarationFQN(method);
if (functionFQN != null) { 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()); Collection<? extends DeclarationDescriptor> declarationDescriptors = ImportsResolver.analyseImportReference(importDirective, jetScope, new BindingTraceContext());
for (DeclarationDescriptor declarationDescriptor : declarationDescriptors) { for (DeclarationDescriptor declarationDescriptor : declarationDescriptors) {
if (declarationDescriptor instanceof FunctionDescriptor) { 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.JetType;
import org.jetbrains.jet.lang.types.lang.JetStandardLibrary; import org.jetbrains.jet.lang.types.lang.JetStandardLibrary;
import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -93,7 +93,7 @@ public abstract class OverrideImplementMethodsHandler implements LanguageCodeIns
bodyBuilder.append("val "); bodyBuilder.append("val ");
} }
bodyBuilder.append(descriptor.getName()).append(":").append(descriptor.getType()); 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()); String initializer = defaultInitializer(descriptor.getType(), JetStandardLibrary.getInstance());
if (initializer != null) { if (initializer != null) {
bodyBuilder.append("=").append(initializer); bodyBuilder.append("=").append(initializer);
@@ -118,14 +118,14 @@ public abstract class OverrideImplementMethodsHandler implements LanguageCodeIns
bodyBuilder.append(":"); bodyBuilder.append(":");
bodyBuilder.append(parameterDescriptor.getType().toString()); bodyBuilder.append(parameterDescriptor.getType().toString());
ImportClassHelper.addImportDirectiveIfNeeded(parameterDescriptor.getType(), file); ImportInsertHelper.addImportDirectiveIfNeeded(parameterDescriptor.getType(), file);
} }
bodyBuilder.append(")"); bodyBuilder.append(")");
final JetType returnType = descriptor.getReturnType(); final JetType returnType = descriptor.getReturnType();
final JetStandardLibrary stdlib = JetStandardLibrary.getInstance(); final JetStandardLibrary stdlib = JetStandardLibrary.getInstance();
if (!returnType.equals(stdlib.getTuple0Type())) { if (!returnType.equals(stdlib.getTuple0Type())) {
bodyBuilder.append(":").append(returnType.toString()); bodyBuilder.append(":").append(returnType.toString());
ImportClassHelper.addImportDirectiveIfNeeded(returnType, file); ImportInsertHelper.addImportDirectiveIfNeeded(returnType, file);
} }
bodyBuilder.append("{").append("throw UnsupportedOperationException()").append("}"); 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.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetImportDirective; 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.psi.JetQualifiedExpression;
import org.jetbrains.jet.lang.resolve.DescriptorUtils; import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.FqName; import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.plugin.completion.JetLookupObject; 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: * Performs number of code modification after insertion jet function:
@@ -135,7 +134,7 @@ public class JetFunctionInsertHandler implements InsertHandler<LookupElement> {
@Override @Override
public void run() { public void run() {
final FqName fqn = DescriptorUtils.getFQName(functionDescriptor).toSafe(); 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.InsertionContext;
import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement; import com.intellij.codeInsight.completion.JavaPsiClassReferenceElement;
import org.jetbrains.jet.lang.psi.JetFile; 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. * 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) { public void handleInsert(final InsertionContext context, final JavaPsiClassReferenceElement item) {
if (context.getFile() instanceof JetFile) { if (context.getFile() instanceof JetFile) {
final JetFile jetFile = (JetFile) context.getFile(); final JetFile jetFile = (JetFile) context.getFile();
ImportClassHelper.addImportDirective(item.getQualifiedName(), jetFile); ImportInsertHelper.addImportDirective(new FqName(item.getQualifiedName()), jetFile);
} }
// check annotation // check annotation
@@ -17,19 +17,20 @@
package org.jetbrains.jet.plugin.editor.importOptimizer; package org.jetbrains.jet.plugin.editor.importOptimizer;
import com.intellij.lang.ImportOptimizer; import com.intellij.lang.ImportOptimizer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.psi.*; import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*; 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.lang.resolve.java.JvmAbi;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
import org.jetbrains.jet.util.QualifiedNamesUtil; import org.jetbrains.jet.util.QualifiedNamesUtil;
import java.util.ArrayList; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** /**
* @author Nikolay Krasko * @author Nikolay Krasko
@@ -47,17 +48,79 @@ public class JetImportOptimizer implements ImportOptimizer {
@Override @Override
public void run() { public void run() {
JetFile jetFile = (JetFile) file; final JetFile jetFile = (JetFile) file;
Set<String> usedQualifiedNames = extractUsedQualifiedNames(jetFile); 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() { jetFile.accept(new JetVisitorVoid() {
@Override @Override
public void visitElement(PsiElement element) { public void visitElement(PsiElement element) {
@@ -83,7 +146,7 @@ public class JetImportOptimizer implements ImportOptimizer {
} }
for (PsiElement psiReference : references) { for (PsiElement psiReference : references) {
String fqName = getElementFQName(psiReference); FqName fqName = getElementFQName(psiReference);
if (fqName != null) { if (fqName != null) {
usedQualifiedNames.add(fqName); usedQualifiedNames.add(fqName);
} }
@@ -101,7 +164,7 @@ public class JetImportOptimizer implements ImportOptimizer {
@Nullable @Nullable
public static String getElementFQName(PsiElement element) { public static FqName getElementFQName(PsiElement element) {
if (element instanceof JetClassOrObject) { if (element instanceof JetClassOrObject) {
return JetPsiUtil.getFQName((JetClassOrObject) element); return JetPsiUtil.getFQName((JetClassOrObject) element);
} }
@@ -109,7 +172,10 @@ public class JetImportOptimizer implements ImportOptimizer {
return JetPsiUtil.getFQName((JetNamedFunction) element); return JetPsiUtil.getFQName((JetNamedFunction) element);
} }
if (element instanceof PsiClass) { if (element instanceof PsiClass) {
return ((PsiClass) element).getQualifiedName(); String qualifiedName = ((PsiClass) element).getQualifiedName();
if (qualifiedName != null) {
return new FqName(qualifiedName);
}
} }
if (element instanceof PsiMethod) { if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod) element; PsiMethod method = (PsiMethod) element;
@@ -117,15 +183,14 @@ public class JetImportOptimizer implements ImportOptimizer {
PsiClass containingClass = method.getContainingClass(); PsiClass containingClass = method.getContainingClass();
if (containingClass != null) { if (containingClass != null) {
String classFQN = containingClass.getQualifiedName(); String classFQNStr = containingClass.getQualifiedName();
if (classFQNStr != null) {
if (classFQN != null) { FqName classFQN = new FqName(classFQNStr);
if (QualifiedNamesUtil.fqnToShortName(classFQN).equals(JvmAbi.PACKAGE_CLASS)) { if (classFQN.shortName().equals(JvmAbi.PACKAGE_CLASS)) {
String classParentFQN = QualifiedNamesUtil.withoutLastSegment(classFQN); return QualifiedNamesUtil.combine(classFQN.parent(), method.getName());
return QualifiedNamesUtil.combine(classParentFQN, method.getName());
} }
else { 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; assert element instanceof JetFunction;
newElement = addFunctionType(project, (JetFunction) element, type); newElement = addFunctionType(project, (JetFunction) element, type);
} }
ImportClassHelper.addImportDirectiveIfNeeded(type, (JetFile) file); ImportInsertHelper.addImportDirectiveIfNeeded(type, (JetFile) file);
element.replace(newElement); element.replace(newElement);
} }
@@ -225,13 +225,7 @@ public class ImportClassAndFunFix extends JetHintAction<JetSimpleNameExpression>
@NotNull @NotNull
private JetAddImportAction createAction(@NotNull Project project, @NotNull Editor editor) { private JetAddImportAction createAction(@NotNull Project project, @NotNull Editor editor) {
Collection<String> suggesionStrings = Collections2.transform(suggestions, new Function<FqName, String>() { return new JetAddImportAction(project, editor, element, suggestions);
@Override
public String apply(FqName fqName) {
return fqName.getFqName();
}
});
return new JetAddImportAction(project, editor, element, suggesionStrings);
} }
@Nullable @Nullable
@@ -18,10 +18,12 @@ package org.jetbrains.jet.plugin.quickfix;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.DefaultModuleConfiguration; import org.jetbrains.jet.lang.DefaultModuleConfiguration;
import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.FqName; 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.AnalyzerFacadeForJVM;
import org.jetbrains.jet.lang.resolve.java.JavaBridgeConfiguration; import org.jetbrains.jet.lang.resolve.java.JavaBridgeConfiguration;
import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver; import org.jetbrains.jet.lang.resolve.java.JavaDescriptorResolver;
@@ -35,7 +37,10 @@ import java.util.List;
/** /**
* @author svtk * @author svtk
*/ */
public class ImportClassHelper { public class ImportInsertHelper {
private ImportInsertHelper() {
}
/** /**
* Add import directive corresponding to a type to file when it is needed. * 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 if (element != null && element.getContainingFile() == file) { //declaration is in the same file, so no import is needed
return; return;
} }
addImportDirective(JetPluginUtil.computeTypeFullName(type).getFqName(), file); addImportDirective(JetPluginUtil.computeTypeFullName(type), file);
} }
/** /**
* Add import directive into the PSI tree for the given namespace. * 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. * @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 public static void addImportDirective(@NotNull ImportPath importPath, @Nullable String aliasName, @NotNull JetFile file) {
if (importString.startsWith(JavaDescriptorResolver.JAVA_ROOT + ".")) {
importString = importString.substring((JavaDescriptorResolver.JAVA_ROOT + ".").length()); 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; 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()) { if (!importDirectives.isEmpty()) {
// Check if import is already present // Check if import is already present
for (JetImportDirective directive : importDirectives) { for (JetImportDirective directive : importDirectives) {
String importPath = JetPsiUtil.getImportPath(directive); ImportPath existentImportPath = JetPsiUtil.getImportPath(directive);
if (importPath != null && QualifiedNamesUtil.isImported(importPath, importString)) { if (existentImportPath != null &&
directive.getAliasName() == null &&
QualifiedNamesUtil.isImported(existentImportPath, importPath)) {
return; return;
} }
} }
@@ -101,25 +112,40 @@ public class ImportClassHelper {
} }
} }
// Check that import is useless /**
private static boolean isImportedByDefault(@NotNull String importString, @NotNull FqName filePackageFqn) { * Check that import is useless.
if (QualifiedNamesUtil.isOneSegmentFQN(importString) || */
filePackageFqn.getFqName().equals(QualifiedNamesUtil.withoutLastSegment(importString))) { private static boolean isImportedByDefault(@NotNull ImportPath importPath, String aliasName, @NotNull FqName filePackageFqn) {
if (importPath.fqnPart().isRoot()) {
return true; return true;
} }
for (String defaultJetImport : DefaultModuleConfiguration.DEFAULT_JET_IMPORTS) { if (aliasName != null) {
if (QualifiedNamesUtil.isImported(defaultJetImport, importString)) { 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; return true;
} }
} }
for (String defaultJavaImport : JavaBridgeConfiguration.DEFAULT_JAVA_IMPORTS) { for (ImportPath defaultJavaImport : JavaBridgeConfiguration.DEFAULT_JAVA_IMPORTS) {
if (QualifiedNamesUtil.isImported(defaultJavaImport + ".*", importString)) { if (QualifiedNamesUtil.isImported(defaultJavaImport, importPath)) {
return true; return true;
} }
} }
return false; return false;
} }
} }
@@ -119,7 +119,7 @@ public class RemovePartsFromPropertyFix extends JetIntentionAction<JetProperty>
} }
} }
if (needImport) { if (needImport) {
ImportClassHelper.addImportDirectiveIfNeeded(type, (JetFile)file); ImportInsertHelper.addImportDirectiveIfNeeded(type, (JetFile) file);
} }
element.replace(newElement); element.replace(newElement);
} }
@@ -19,6 +19,7 @@ package org.jetbrains.jet.plugin.quickfix;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase; import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.plugin.PluginTestCaseBase; import org.jetbrains.jet.plugin.PluginTestCaseBase;
import java.io.IOException; import java.io.IOException;
@@ -28,19 +29,19 @@ import java.io.IOException;
*/ */
public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase { public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase {
public void testDoNotImportIfGeneralExist() { public void testDoNotImportIfGeneralExist() {
testImportInFile("jettesting.data.testFunction"); doFileTest("jettesting.data.testFunction");
} }
public void testDoNotImportIfGeneralSpaceExist() { public void testDoNotImportIfGeneralSpaceExist() {
testImportInFile("jettesting.data.testFunction"); doFileTest("jettesting.data.testFunction");
} }
public void testNoDefaultImport() { public void testNoDefaultImport() {
testImportInFile("kotlin.io.println"); doFileTest("kotlin.io.println");
} }
public void testImportBeforeObject() { public void testImportBeforeObject() {
testImportInFile("java.util.HashSet"); doFileTest("java.util.HashSet");
} }
public void testInsertInEmptyFile() throws IOException { public void testInsertInEmptyFile() throws IOException {
@@ -48,7 +49,7 @@ public class ImportClassHelperTest extends LightDaemonAnalyzerTestCase {
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override @Override
public void run() { 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() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override @Override
public void run() { 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"); checkResultByText("package some\n\nimport java.util.ArrayList");
} }
public void testImportInFile(final String importString) { public void doFileTest(final String importString) {
configureByFile(getTestName(false) + ".kt"); configureByFile(getTestName(false) + ".kt");
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override @Override
public void run() { public void run() {
ImportClassHelper.addImportDirective(importString, (JetFile) getFile()); ImportInsertHelper.addImportDirective(new FqName(importString), (JetFile) getFile());
} }
}); });
checkResultByFile(getTestName(false) + ".kt.after"); checkResultByFile(getTestName(false) + ".kt.after");