Drop pre-1.0 whole-project syntax migration actions

This commit is contained in:
Dmitry Jemerov
2017-03-27 14:57:44 +02:00
parent d615aba4cf
commit d8dfad875d
13 changed files with 3 additions and 468 deletions
@@ -20,11 +20,10 @@ import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.addConstructorKeyword
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionForFirstParentOfType
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPrimaryConstructor
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.addConstructorKeyword
class MissingConstructorKeywordFix(element: KtPrimaryConstructor) : KotlinQuickFixAction<KtPrimaryConstructor>(element), CleanupFix {
override fun getFamilyName(): String = text
@@ -37,13 +36,5 @@ class MissingConstructorKeywordFix(element: KtPrimaryConstructor) : KotlinQuickF
companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? =
diagnostic.createIntentionForFirstParentOfType(::MissingConstructorKeywordFix)
fun createWholeProjectFixFactory(): KotlinSingleIntentionActionFactory = createIntentionFactory {
WholeProjectForEachElementOfTypeFix.createByPredicate<KtPrimaryConstructor>(
predicate = { it.modifierList != null && !it.hasConstructorKeyword() },
taskProcessor = { it.addConstructorKeyword() },
name = "Add missing 'constructor' keyword in whole project"
)
}
}
}
@@ -377,13 +377,11 @@ class QuickFixRegistrar : QuickFixContributor {
EXPLICIT_DELEGATION_CALL_REQUIRED.registerFactory(InsertDelegationCallQuickfix.InsertThisDelegationCallFactory)
EXPLICIT_DELEGATION_CALL_REQUIRED.registerFactory(InsertDelegationCallQuickfix.InsertSuperDelegationCallFactory)
MISSING_CONSTRUCTOR_KEYWORD.registerFactory(MissingConstructorKeywordFix,
MissingConstructorKeywordFix.createWholeProjectFixFactory())
MISSING_CONSTRUCTOR_KEYWORD.registerFactory(MissingConstructorKeywordFix)
ANONYMOUS_FUNCTION_WITH_NAME.registerFactory(RemoveNameFromFunctionExpressionFix)
UNRESOLVED_REFERENCE.registerFactory(ReplaceObsoleteLabelSyntaxFix,
ReplaceObsoleteLabelSyntaxFix.createWholeProjectFixFactory())
UNRESOLVED_REFERENCE.registerFactory(ReplaceObsoleteLabelSyntaxFix)
DEPRECATION.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
DEPRECATION_ERROR.registerFactory(DeprecatedSymbolUsageFix, DeprecatedSymbolUsageInWholeProjectFix)
@@ -21,13 +21,8 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.quickfix.quickfixUtil.createIntentionFactory
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
class ReplaceObsoleteLabelSyntaxFix(element: KtAnnotationEntry) : KotlinQuickFixAction<KtAnnotationEntry>(element), CleanupFix {
override fun getFamilyName(): String = "Update obsolete label syntax"
@@ -46,37 +41,6 @@ class ReplaceObsoleteLabelSyntaxFix(element: KtAnnotationEntry) : KotlinQuickFix
return ReplaceObsoleteLabelSyntaxFix(annotationEntry)
}
fun createWholeProjectFixFactory(): KotlinSingleIntentionActionFactory = createIntentionFactory factory@ {
diagnostic ->
if (!(diagnostic.psiElement.getNonStrictParentOfType<KtAnnotationEntry>()?.looksLikeObsoleteLabelWithReferencesInCode()
?: false)) return@factory null
WholeProjectForEachElementOfTypeFix.createForMultiTaskOnElement<KtAnnotatedExpression, KtAnnotationEntry>(
tasksFactory = { collectTasks(it) },
tasksProcessor ={ it.forEach { ann -> replaceWithLabel(ann) } },
name = "Update obsolete label syntax in whole project"
)
}
private fun collectTasks(expression: KtAnnotatedExpression) =
expression.annotationEntries.filter { it.looksLikeObsoleteLabelWithReferencesInCode() }
private fun KtAnnotationEntry.looksLikeObsoleteLabelWithReferencesInCode(): Boolean {
if (!looksLikeObsoleteLabel(this)) return false
val baseExpression = (parent as? KtAnnotatedExpression)?.baseExpression ?: return false
val nameExpression = calleeExpression?.constructorReferenceExpression ?: return false
val labelName = nameExpression.getReferencedName()
return baseExpression.anyDescendantOfType<KtExpressionWithLabel> {
(it is KtBreakExpression || it is KtContinueExpression || it is KtReturnExpression) &&
it.getLabelName() == labelName &&
it.getTargetLabel()?.analyze()?.get(BindingContext.LABEL_TARGET, it.getTargetLabel()) == null
} && analyze().diagnostics.forElement(nameExpression).any { it.factory == Errors.UNRESOLVED_REFERENCE }
}
fun looksLikeObsoleteLabel(entry: KtAnnotationEntry): Boolean =
entry.atSymbol != null &&
entry.parent is KtAnnotatedExpression &&
@@ -1,158 +0,0 @@
/*
* 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.quickfix
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.project.PluginJetFilesProvider
import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtVisitorVoid
import org.jetbrains.kotlin.psi.psiUtil.flatMapDescendantsOfTypeVisitor
import java.util.*
abstract class WholeProjectModalAction<TData : Any>(val title: String) : IntentionAction {
private val LOG = Logger.getInstance(WholeProjectModalAction::class.java)
override final fun startInWriteAction() = false
override final fun invoke(project: Project, editor: Editor?, file: PsiFile?) = invoke(project)
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?) = true
private fun invoke(project: Project) =
ProgressManager.getInstance().run(
object : Task.Modal(project, title, true) {
override fun run(indicator: ProgressIndicator) {
val filesToData = HashMap<KtFile, TData>()
runReadAction (fun() {
val files = PluginJetFilesProvider.allFilesInProject(project)
for ((i, currentFile) in files.withIndex()) {
indicator.text = "Checking file $i of ${files.size}..."
indicator.text2 = currentFile.virtualFile.path
indicator.fraction = (i + 1) / files.size.toDouble()
try {
val data = collectDataForFile(project, currentFile)
if (data != null) filesToData[currentFile] = data
}
catch (e: ProcessCanceledException) {
return
}
catch (e: Throwable) {
LOG.error(e)
}
}
})
applyAll(project, filesToData)
}
})
private fun applyAll(project: Project, filesToData: Map<KtFile, TData>) {
UIUtil.invokeLaterIfNeeded {
project.executeCommand(text) {
runWriteAction {
filesToData.forEach {
try {
applyChangesForFile(project, it.key, it.value)
}
catch (e: Throwable) {
LOG.error(e)
}
}
}
}
}
}
// this method will be started under read action
protected abstract fun collectDataForFile(project: Project, file: KtFile): TData?
// this method will be started under write action
protected abstract fun applyChangesForFile(project: Project, file: KtFile, data: TData)
}
abstract class WholeProjectModalByCollectionAction<TTask : Any>(modalTitle: String)
: WholeProjectModalAction<Collection<TTask>>(modalTitle) {
override fun collectDataForFile(project: Project, file: KtFile): Collection<TTask>? {
val accumulator = arrayListOf<TTask>()
collectTasksForFile(project, file, accumulator)
return if (!accumulator.isEmpty()) accumulator else null
}
abstract fun collectTasksForFile(project: Project, file: KtFile, accumulator: MutableCollection<TTask>)
}
internal class WholeProjectForEachElementOfTypeFix<TTask : Any> private constructor(
private val collectingVisitorFactory: (MutableCollection<TTask>) -> KtVisitorVoid,
private val tasksProcessor: (Collection<TTask>) -> Unit,
private val name: String,
private val familyName: String = name
) : WholeProjectModalByCollectionAction<TTask>("Applying '$name'") {
override fun getFamilyName() = familyName
override fun getText() = name
override fun collectTasksForFile(project: Project, file: KtFile, accumulator: MutableCollection<TTask>) {
file.accept(collectingVisitorFactory(accumulator))
}
override fun applyChangesForFile(project: Project, file: KtFile, data: Collection<TTask>) = tasksProcessor(data)
companion object {
inline fun <reified TElement : KtElement> createByPredicate(
noinline predicate: (TElement) -> Boolean,
noinline taskProcessor: (TElement) -> Unit,
name: String
) = createByTaskFactory<TElement, TElement>(
taskFactory = { if (predicate(it)) it else null },
taskProcessor = taskProcessor,
name = name
)
inline fun <reified TElement : KtElement, TTask : Any> createByTaskFactory(
noinline taskFactory: (TElement) -> TTask?,
noinline taskProcessor: (TTask) -> Unit,
name: String
) = createForMultiTaskOnElement<TElement, TTask>(
tasksFactory = { listOfNotNull(taskFactory(it)) },
tasksProcessor = { it.forEach(taskProcessor) },
name = name
)
inline fun <reified TElement : KtElement, TTask : Any> createForMultiTaskOnElement(
noinline tasksFactory: (TElement) -> Collection<TTask>,
noinline tasksProcessor: (Collection<TTask>) -> Unit,
name: String
) = WholeProjectForEachElementOfTypeFix(
collectingVisitorFactory = { accumulator -> flatMapDescendantsOfTypeVisitor(accumulator, tasksFactory) },
tasksProcessor = tasksProcessor,
name = name,
familyName = name
)
}
}
@@ -1 +0,0 @@
class Q private constructor()
@@ -1,15 +0,0 @@
// "Add missing 'constructor' keyword in whole project" "true"
// ERROR: Use 'constructor' keyword after modifiers of primary constructor
// ERROR: Use 'constructor' keyword after modifiers of primary constructor
// ERROR: Use 'constructor' keyword after modifiers of primary constructor
annotation class Ann(val x: Int = 1)
class A @Ann(1)private constructor(x: Int) {
inner class B() // do not insert here
inner class C protected constructor() {
fun foo() {
class Local private constructor()
}
}
}
@@ -1,15 +0,0 @@
// "Add missing 'constructor' keyword in whole project" "true"
// ERROR: Use 'constructor' keyword after modifiers of primary constructor
// ERROR: Use 'constructor' keyword after modifiers of primary constructor
// ERROR: Use 'constructor' keyword after modifiers of primary constructor
annotation class Ann(val x: Int = 1)
class A @Ann(1)<caret>private (x: Int) {
inner class B() // do not insert here
inner class C protected () {
fun foo() {
class Local private()
}
}
}
@@ -1 +0,0 @@
class Q private()
@@ -1,5 +0,0 @@
fun bar() {
l@ while (true) {
break@l
}
}
@@ -1,94 +0,0 @@
// "Update obsolete label syntax in whole project" "true"
// ERROR: Unresolved reference: @abc
// ERROR: Unresolved reference: @ann
// ERROR: Unresolved reference: @cde
// ERROR: Unresolved reference: @labeled
// ERROR: Unresolved reference: @loop
// ERROR: Unresolved reference: @loop2
// ERROR: Unresolved reference: @loop3
// ERROR: Unresolved reference: @loop4
// ERROR: Unresolved reference: @loop5
// ERROR: Unresolved reference: abc
// ERROR: Unresolved reference: cde
// ERROR: Unresolved reference: labeled
// ERROR: Unresolved reference: loop
// ERROR: Unresolved reference: loop1
// ERROR: Unresolved reference: loop2
// ERROR: Unresolved reference: loop2
// ERROR: Unresolved reference: loop3
// ERROR: Unresolved reference: loop4
// ERROR: Unresolved reference: loop5
// ERROR: Unresolved reference: noreferences
// ERROR: Unresolved reference: notLabelAnnotation
// ERROR: Unresolved reference: notLoop
// ERROR: The label '@abc' does not denote a loop
// ERROR: The label '@ann' does not denote a loop
// ERROR: The label '@cde' does not denote a loop
// ERROR: The label '@loop' does not denote a loop
// ERROR: The label '@loop2' does not denote a loop
// ERROR: The label '@loop3' does not denote a loop
// ERROR: The label '@loop4' does not denote a loop
// ERROR: The label '@loop5' does not denote a loop
fun run(block: () -> Unit) = block()
annotation class ann
@notLabelAnnotation class A {
fun foo() {
loop@
for (i in 1..100) {
/* comment */
continue@loop
}
@noreferences for (i in 1..100) {
val x = 1
}
@loop1 for (i in 1..100) {
loop1@ for (j in 1..100) {
continue@loop1
}
}
loop2@ for (i in 1..100) {
loop2@ for (j in 1..100) {
break@loop2
}
}
@[loop3] for (i in 1..100) {
continue@loop3
}
run() labeled@ {
return@labeled
}
@ann for (i in 1..100) {
continue@ann
}
@ for (i in 1..100) {
break@
}
@loop4() for (i in 1..100) {
continue@loop4
}
@notLoop class Local
@abc @cde for (i in 1..100) {
break@cde
break@abc
}
/* 123 */ loop5@ /* 456 */
for (i in 1..100) {
break@loop5
} /* 789 */
}
}
@@ -1,94 +0,0 @@
// "Update obsolete label syntax in whole project" "true"
// ERROR: Unresolved reference: @abc
// ERROR: Unresolved reference: @ann
// ERROR: Unresolved reference: @cde
// ERROR: Unresolved reference: @labeled
// ERROR: Unresolved reference: @loop
// ERROR: Unresolved reference: @loop2
// ERROR: Unresolved reference: @loop3
// ERROR: Unresolved reference: @loop4
// ERROR: Unresolved reference: @loop5
// ERROR: Unresolved reference: abc
// ERROR: Unresolved reference: cde
// ERROR: Unresolved reference: labeled
// ERROR: Unresolved reference: loop
// ERROR: Unresolved reference: loop1
// ERROR: Unresolved reference: loop2
// ERROR: Unresolved reference: loop2
// ERROR: Unresolved reference: loop3
// ERROR: Unresolved reference: loop4
// ERROR: Unresolved reference: loop5
// ERROR: Unresolved reference: noreferences
// ERROR: Unresolved reference: notLabelAnnotation
// ERROR: Unresolved reference: notLoop
// ERROR: The label '@abc' does not denote a loop
// ERROR: The label '@ann' does not denote a loop
// ERROR: The label '@cde' does not denote a loop
// ERROR: The label '@loop' does not denote a loop
// ERROR: The label '@loop2' does not denote a loop
// ERROR: The label '@loop3' does not denote a loop
// ERROR: The label '@loop4' does not denote a loop
// ERROR: The label '@loop5' does not denote a loop
fun run(block: () -> Unit) = block()
annotation class ann
@notLabelAnnotation class A {
fun foo() {
@loop<caret>
for (i in 1..100) {
/* comment */
continue@loop
}
@noreferences for (i in 1..100) {
val x = 1
}
@loop1 for (i in 1..100) {
loop1@ for (j in 1..100) {
continue@loop1
}
}
@loop2 for (i in 1..100) {
@loop2 for (j in 1..100) {
break@loop2
}
}
@[loop3] for (i in 1..100) {
continue@loop3
}
run() @labeled {
return@labeled
}
@ann for (i in 1..100) {
continue@ann
}
@ for (i in 1..100) {
break@
}
@loop4() for (i in 1..100) {
continue@loop4
}
@notLoop class Local
@abc @cde for (i in 1..100) {
break@cde
break@abc
}
/* 123 */ @loop5 /* 456 */
for (i in 1..100) {
break@loop5
} /* 789 */
}
}
@@ -1,5 +0,0 @@
fun bar() {
@l while (true) {
break@l
}
}
@@ -1722,36 +1722,6 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
}
}
@TestMetadata("idea/testData/quickfix/migration/missingConstructorKeyword")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class MissingConstructorKeyword extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInMissingConstructorKeyword() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/missingConstructorKeyword"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
@TestMetadata("manyFilesMuitliple.before.Main.kt")
public void testManyFilesMuitliple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/missingConstructorKeyword/manyFilesMuitliple.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
@TestMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class ObsoleteLabelSyntax extends AbstractQuickFixMultiFileTest {
public void testAllFilesPresentInObsoleteLabelSyntax() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/migration/obsoleteLabelSyntax"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), TargetBackend.ANY, true);
}
@TestMetadata("manyFilesMuitliple.before.Main.kt")
public void testManyFilesMuitliple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/migration/obsoleteLabelSyntax/manyFilesMuitliple.before.Main.kt");
doTestWithExtraFile(fileName);
}
}
}
@TestMetadata("idea/testData/quickfix/modifiers")