diff --git a/idea/resources/inspectionDescriptions/OldStdlibApi.html b/idea/resources/inspectionDescriptions/OldStdlibApi.html new file mode 100644 index 00000000000..6ad0f3fba94 --- /dev/null +++ b/idea/resources/inspectionDescriptions/OldStdlibApi.html @@ -0,0 +1,5 @@ + + +This inspection located pre-1.0 usages of the Kotlin standard library APIs from Java code and replaces them with up-to-date APIs. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 55e1e8b9716..8df3e7acd13 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1301,6 +1301,13 @@ enabledByDefault="true" level="WARNING"/> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/OldStdlibApiInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/OldStdlibApiInspection.kt new file mode 100644 index 00000000000..cdff160a6ad --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/OldStdlibApiInspection.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.inspections + +import com.intellij.codeInspection.* +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project +import com.intellij.psi.* +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.search.allScope +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType + +class OldStdlibApiInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return object : PsiElementVisitor() { + override fun visitElement(element: PsiElement) { + if (element.language == KotlinLanguage.INSTANCE) return + val importStatement = element.getParentOfType(false) + if (importStatement != null) return + + for (reference in element.references) { + checkReference(reference)?.let { + holder.registerProblem(element, "Usage of the Kotlin standard library through a deprecated qualified name", + ProblemHighlightType.LIKE_DEPRECATED, it) + + } + } + } + } + } + + private fun checkReference(reference: PsiReference): LocalQuickFix? { + val resolveResult = reference.resolve() + if (resolveResult is PsiClass) { + val fqName = resolveResult.qualifiedName + val newFqName = StdlibMigrationMap.classMap[fqName] + if (newFqName != null) { + return OldStdlibApiFix { project, scope -> + JavaPsiFacade.getInstance(project).findClass(newFqName, scope) + } + } + } + else if (resolveResult is PsiMethod) { + val containingClass = resolveResult.containingClass ?: return null + val fqName = MethodFQName(containingClass.qualifiedName ?: return null, resolveResult.name) + val newFqName = StdlibMigrationMap.methodMap[fqName] + if (newFqName != null) { + return OldStdlibApiFix { project, scope -> + JavaPsiFacade.getInstance(project).findClass(newFqName.className, scope) + ?.findMethodsByName(newFqName.methodName, false) + ?.singleOrNull() + } + } + } + return null + } +} + +public class OldStdlibApiFix(val newElementCallback: (Project, GlobalSearchScope) -> PsiElement?) : LocalQuickFix { + override fun getName(): String = "Replace with new qualified name" + override fun getFamilyName(): String = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val element = descriptor.psiElement + val reference = element.reference ?: return + val module = ModuleUtil.findModuleForPsiElement(element) + val scope = module?.moduleWithLibrariesScope ?: project.allScope() + + val newElement = newElementCallback(project, scope) ?: return + val newReference = reference.bindToElement(newElement) + + val javaFile = newReference.containingFile as? PsiJavaFile + if (javaFile != null) { + JavaCodeStyleManager.getInstance(project).removeRedundantImports(javaFile) + } + + JavaCodeStyleManager.getInstance(project).shortenClassReferences(newReference) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/StdlibMigrationMap.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/StdlibMigrationMap.kt new file mode 100644 index 00000000000..18da5683134 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/StdlibMigrationMap.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2015 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.kotlin.idea.inspections + +data class MethodFQName(val className: String, val methodName: String) + +object StdlibMigrationMap { + val methodMap = hashMapOf( + MethodFQName("kotlin.jvm.ClassMapping", "getKotlin") to MethodFQName("kotlin.jvm.JvmClassMappingKt", "getKotlinClass"), + MethodFQName("kotlin.jvm.ClassMapping", "getJava") to MethodFQName("kotlin.jvm.JvmClassMappingKt", "getJavaClass") + ) + + val classMap = hashMapOf( + "kotlin.ArraysKt" to "kotlin.collections.ArraysKt", + "kotlin.CharsKt" to "kotlin.text.CharsKt", + "kotlin.CollectionsKt" to "kotlin.collections.CollectionsKt", + "kotlin.MapsKt" to "kotlin.collections.MapsKt", + "kotlin.RangesKt" to "kotlin.ranges.RangesKt", + "kotlin.SequencesKt" to "kotlin.sequences.SequencesKt", + "kotlin.SetsKt" to "kotlin.collections.SetsKt", + "kotlin.StringsKt" to "kotlin.text.StringsKt", + "kotlin.support.AbstractIterator" to "kotlin.collections.AbstractIterator" + ) +} diff --git a/idea/testData/quickfix/migration/stdlib/.inspection b/idea/testData/quickfix/migration/stdlib/.inspection new file mode 100644 index 00000000000..b4534997f49 --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.OldStdlibApiInspection \ No newline at end of file diff --git a/idea/testData/quickfix/migration/stdlib/basic.after.java b/idea/testData/quickfix/migration/stdlib/basic.after.java new file mode 100644 index 00000000000..b27da5cee60 --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/basic.after.java @@ -0,0 +1,10 @@ +// "Replace with new qualified name" "true" +// WITH_RUNTIME + +import kotlin.collections.ArraysKt; + +class C { + public void foo(byte[] bytes) { + ArraysKt.component1(bytes); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/stdlib/basic.before.Main.java b/idea/testData/quickfix/migration/stdlib/basic.before.Main.java new file mode 100644 index 00000000000..4191a1c4e45 --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/basic.before.Main.java @@ -0,0 +1,10 @@ +// "Replace with new qualified name" "true" +// WITH_RUNTIME + +import kotlin.ArraysKt; + +class C { + public void foo(byte[] bytes) { + ArraysKt.component1(bytes); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/stdlib/importStatic.after.java b/idea/testData/quickfix/migration/stdlib/importStatic.after.java new file mode 100644 index 00000000000..cc134c14b3b --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/importStatic.after.java @@ -0,0 +1,10 @@ +// "Replace with new qualified name" "true" +// WITH_RUNTIME + +import static kotlin.collections.ArraysKt.component1; + +class C { + public void foo(byte[] bytes) { + component1(bytes); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/stdlib/importStatic.before.Main.java b/idea/testData/quickfix/migration/stdlib/importStatic.before.Main.java new file mode 100644 index 00000000000..5162e175ba6 --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/importStatic.before.Main.java @@ -0,0 +1,10 @@ +// "Replace with new qualified name" "true" +// WITH_RUNTIME + +import static kotlin.ArraysKt.component1; + +class C { + public void foo(byte[] bytes) { + component1(bytes); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/stdlib/method.after.java b/idea/testData/quickfix/migration/stdlib/method.after.java new file mode 100644 index 00000000000..cbe2298b0e4 --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/method.after.java @@ -0,0 +1,10 @@ +// "Replace with new qualified name" "true" +// WITH_RUNTIME + +import kotlin.jvm.JvmClassMappingKt; + +class C { + public void foo(Class cls) { + JvmClassMappingKt.getKotlinClass(cls); + } +} \ No newline at end of file diff --git a/idea/testData/quickfix/migration/stdlib/method.before.Main.java b/idea/testData/quickfix/migration/stdlib/method.before.Main.java new file mode 100644 index 00000000000..566e1d2490b --- /dev/null +++ b/idea/testData/quickfix/migration/stdlib/method.before.Main.java @@ -0,0 +1,10 @@ +// "Replace with new qualified name" "true" +// WITH_RUNTIME + +import kotlin.jvm.ClassMapping; + +class C { + public void foo(Class cls) { + ClassMapping.getKotlin(cls); + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index a2b2625c5c4..9d465b30a81 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -1387,6 +1387,33 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes } } + @TestMetadata("idea/testData/quickfix/migration/stdlib") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Stdlib extends AbstractQuickFixMultiFileTest { + public void testAllFilesPresentInStdlib() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/stdlib"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), true); + } + + @TestMetadata("basic.before.Main.java") + public void testBasic() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/stdlib/basic.before.Main.java"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("importStatic.before.Main.java") + public void testImportStatic() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/stdlib/importStatic.before.Main.java"); + doTestWithExtraFile(fileName); + } + + @TestMetadata("method.before.Main.java") + public void testMethod() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/stdlib/method.before.Main.java"); + doTestWithExtraFile(fileName); + } + } + } @TestMetadata("idea/testData/quickfix/modifiers")