From 8c034d4ac767a7b4fa5d93c84a6fe6de0930a930 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Tue, 17 Feb 2015 14:20:39 +0300 Subject: [PATCH] New optimize imports algorithm supports *-imports code style --- .../kotlin/idea/util/ImportInsertHelper.kt | 1 + .../idea/imports/KotlinImportOptimizer.kt | 73 +++++++++++++++++-- .../allUnderImports/ClassNameConflict.kt | 10 +++ .../ClassNameConflict.kt.after | 9 +++ .../allUnderImports/ClassNameConflict2.kt | 10 +++ .../ClassNameConflict2.kt.after | 10 +++ .../ClassNameConflictWithCurrentPackage.kt | 10 +++ ...assNameConflictWithCurrentPackage.kt.after | 10 +++ .../ClassNameConflictWithCurrentPackage2.kt | 9 +++ ...ssNameConflictWithCurrentPackage2.kt.after | 9 +++ .../ClassNameConflictWithDefault.kt | 8 ++ .../ClassNameConflictWithDefault.kt.after | 8 ++ .../allUnderImports/Simple.dependency.kt | 3 + .../optimizeImports/allUnderImports/Simple.kt | 10 +++ .../allUnderImports/Simple.kt.after | 9 +++ .../imports/OptimizeImportsTestGenerated.java | 47 ++++++++++++ 16 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt.after create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt.after create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt.after create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt.after create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt.after create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/Simple.dependency.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/Simple.kt create mode 100644 idea/testData/editor/optimizeImports/allUnderImports/Simple.kt.after diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt index 89a63e7f2db..7d0116175fc 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ImportInsertHelper.kt @@ -29,6 +29,7 @@ public abstract class ImportInsertHelper { public abstract fun optimizeImportsOnTheFly(file: JetFile): Boolean + /*TODO: implementation is not quite correct*/ public abstract fun isImportedWithDefault(importPath: ImportPath, contextFile: JetFile): Boolean public abstract fun mayImportByCodeStyle(descriptor: DeclarationDescriptor): Boolean diff --git a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt index 72beac39d85..264da989e72 100644 --- a/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/imports/KotlinImportOptimizer.kt @@ -24,13 +24,15 @@ import com.intellij.psi.PsiFile import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.name.* import org.jetbrains.kotlin.idea.references.JetReference -import org.jetbrains.kotlin.idea.util.ImportInsertHelper import java.util.* import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.formatter.JetCodeStyleSettings import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.idea.caches.resolve.* +import org.jetbrains.kotlin.resolve.scopes.* +import org.jetbrains.kotlin.idea.util.* public class KotlinImportOptimizer() : ImportOptimizer { @@ -68,17 +70,76 @@ public class KotlinImportOptimizer() : ImportOptimizer { val descriptorsToImport = detectDescriptorsToImport() - val importsToGenerate = ArrayList() + val importsToGenerate = HashSet() if (codeStyleSettings.PREFER_ALL_UNDER_IMPORTS) { - //TODO - //TODO: don't forget that packages may not be imported by * + val packageNames = HashMap() // package name to symbols to import count + + fun incUseCount(packageName: FqName): Int { + val count = packageNames.getOrPut(packageName, { 0 }) + 1 + packageNames.put(packageName, count) + return count + } + + fun decUseCount(packageName: FqName): Int { + val count = packageNames[packageName]!! - 1 + if (count > 0) { + packageNames[packageName] = count + } + else { + packageNames.remove(packageName) + } + return count + } + + val classNames = HashSet() + + val builder = StringBuilder() + builder.append("package ").append(IdeDescriptorRenderers.SOURCE_CODE.renderFqName(currentPackageName)).append("\n") + + for (descriptor in descriptorsToImport) { + val fqName = descriptor.importableFqNameSafe + val parentFqName = fqName.parent() + if (descriptor is PackageViewDescriptor || parentFqName.isRoot()) { + importsToGenerate.add(ImportPath(fqName, false)) + } + else { + val useCount = incUseCount(parentFqName) + + if (descriptor is ClassifierDescriptor) { + classNames.add(fqName) + } + + if (parentFqName == currentPackageName) continue // in current package + if (importInsertHelper.isImportedWithDefault(ImportPath(fqName, false), file)) continue + + if (useCount == 1) { + builder.append("import ").append(IdeDescriptorRenderers.SOURCE_CODE.renderFqName(parentFqName)).append(".*\n") + importsToGenerate.add(ImportPath(parentFqName, true)) + } + } + } + + // now check that all classes are really imported + val fileWithImports = JetPsiFactory(file).createAnalyzableFile("Dummy.kt", builder.toString(), file) + val scope = fileWithImports.getResolutionFacade().getFileTopLevelScope(fileWithImports) + for (fqName in classNames) { + if (scope.getClassifier(fqName.shortName())?.importableFqNameSafe != fqName) { + // add explicit import if failed to import with * (or from current package) + importsToGenerate.add(ImportPath(fqName, false)) + + val packageName = fqName.parent() + if (decUseCount(packageName) == 0) { // all under import is not really needed + importsToGenerate.remove(ImportPath(packageName, true)) + } + } + } } else { for (descriptor in descriptorsToImport) { val fqName = descriptor.importableFqNameSafe - if (descriptor !is PackageViewDescriptor && fqName.parent() == currentPackageName) continue + if (descriptor !is PackageViewDescriptor && fqName.parent() == currentPackageName) continue // in current package val importPath = ImportPath(fqName, false) - if (importInsertHelper.isImportedWithDefault(importPath, file)/*TODO: implementation is not quite correct*/) continue + if (importInsertHelper.isImportedWithDefault(importPath, file)) continue importsToGenerate.add(importPath) } } diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt new file mode 100644 index 00000000000..925f3fbe30b --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt @@ -0,0 +1,10 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.HashMap +import java.util.ArrayList +import java.sql.Date + +fun foo() { + val v = HashMap>() +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt.after b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt.after new file mode 100644 index 00000000000..d4b6c0073b1 --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt.after @@ -0,0 +1,9 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.sql.Date +import java.util.* + +fun foo() { + val v = HashMap>() +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt new file mode 100644 index 00000000000..6023b62cf58 --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt @@ -0,0 +1,10 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.HashMap +import java.sql.Date +import java.sql.Driver + +fun foo() { + val v = HashMap() +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt.after b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt.after new file mode 100644 index 00000000000..c902ff15f4d --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt.after @@ -0,0 +1,10 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.sql.* +import java.sql.Date +import java.util.* + +fun foo() { + val v = HashMap() +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt new file mode 100644 index 00000000000..9ef8ef1a8be --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt @@ -0,0 +1,10 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.ArrayList +import java.util.HashSet + +class ArrayList + +fun foo(p: HashSet) { +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt.after b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt.after new file mode 100644 index 00000000000..97664b69f7d --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt.after @@ -0,0 +1,10 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.* +import java.util.ArrayList + +class ArrayList + +fun foo(p: HashSet) { +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt new file mode 100644 index 00000000000..82871894459 --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt @@ -0,0 +1,9 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.HashSet + +class ArrayList + +fun foo(p: HashSet) { +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt.after b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt.after new file mode 100644 index 00000000000..f2bb1888c0a --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt.after @@ -0,0 +1,9 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.* + +class ArrayList + +fun foo(p: HashSet) { +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt new file mode 100644 index 00000000000..605383c3175 --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt @@ -0,0 +1,8 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.sql.Array +import java.sql.Date + +fun foo(a: Array, d: Date) { +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt.after b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt.after new file mode 100644 index 00000000000..e4818ef1c5d --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt.after @@ -0,0 +1,8 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.sql.* +import java.sql.Array + +fun foo(a: Array, d: Date) { +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/Simple.dependency.kt b/idea/testData/editor/optimizeImports/allUnderImports/Simple.dependency.kt new file mode 100644 index 00000000000..8493cd91f32 --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/Simple.dependency.kt @@ -0,0 +1,3 @@ +package ppp + +class C \ No newline at end of file diff --git a/idea/testData/editor/optimizeImports/allUnderImports/Simple.kt b/idea/testData/editor/optimizeImports/allUnderImports/Simple.kt new file mode 100644 index 00000000000..bce8af5a6d0 --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/Simple.kt @@ -0,0 +1,10 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.util.HashMap +import java.util.ArrayList +import java.io.File + +fun foo(c: C) { + val v = HashMap>() +} diff --git a/idea/testData/editor/optimizeImports/allUnderImports/Simple.kt.after b/idea/testData/editor/optimizeImports/allUnderImports/Simple.kt.after new file mode 100644 index 00000000000..97f3d7c9bfc --- /dev/null +++ b/idea/testData/editor/optimizeImports/allUnderImports/Simple.kt.after @@ -0,0 +1,9 @@ +// ALL_UNDER_IMPORTS: true +package ppp + +import java.io.* +import java.util.* + +fun foo(c: C) { + val v = HashMap>() +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java index 8803dda3c58..3721dba5c6d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/imports/OptimizeImportsTestGenerated.java @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.imports; import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.InnerTestClasses; import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; import org.jetbrains.kotlin.test.JetTestUtils; import org.jetbrains.kotlin.test.TestMetadata; @@ -29,6 +30,7 @@ import java.util.regex.Pattern; @SuppressWarnings("all") @TestMetadata("idea/testData/editor/optimizeImports") @TestDataPath("$PROJECT_ROOT") +@InnerTestClasses({OptimizeImportsTestGenerated.AllUnderImports.class}) @RunWith(JUnit3RunnerWithInners.class) public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { public void testAllFilesPresentInOptimizeImports() throws Exception { @@ -172,4 +174,49 @@ public class OptimizeImportsTestGenerated extends AbstractOptimizeImportsTest { String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/WithAliases.kt"); doTest(fileName); } + + @TestMetadata("idea/testData/editor/optimizeImports/allUnderImports") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AllUnderImports extends AbstractOptimizeImportsTest { + public void testAllFilesPresentInAllUnderImports() throws Exception { + JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/editor/optimizeImports/allUnderImports"), Pattern.compile("^([^\\.]+)\\.kt$"), true); + } + + @TestMetadata("ClassNameConflict.kt") + public void testClassNameConflict() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict.kt"); + doTest(fileName); + } + + @TestMetadata("ClassNameConflict2.kt") + public void testClassNameConflict2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflict2.kt"); + doTest(fileName); + } + + @TestMetadata("ClassNameConflictWithCurrentPackage.kt") + public void testClassNameConflictWithCurrentPackage() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage.kt"); + doTest(fileName); + } + + @TestMetadata("ClassNameConflictWithCurrentPackage2.kt") + public void testClassNameConflictWithCurrentPackage2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithCurrentPackage2.kt"); + doTest(fileName); + } + + @TestMetadata("ClassNameConflictWithDefault.kt") + public void testClassNameConflictWithDefault() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/ClassNameConflictWithDefault.kt"); + doTest(fileName); + } + + @TestMetadata("Simple.kt") + public void testSimple() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/optimizeImports/allUnderImports/Simple.kt"); + doTest(fileName); + } + } }